【问题标题】:Set Window Top+Left in reference to Button position in WPF参考WPF中的按钮位置设置窗口顶部+左侧
【发布时间】:2023-04-02 10:50:01
【问题描述】:

序言

StackOverflow 上已经有几十个关于这个主题的类似问题,我浏览了很多但没有找到适合我的问题的答案。

任务

我有一个 MVVM 模式的 WPF 窗口,它有很多可以打开其他窗口的按钮。我希望我的大部分窗口都与我的按钮相关(我在 MainWindow 的右上角有一个工具栏,并且希望大多数较小的窗口出现在我的按钮正下方),或者至少在同一个窗口中屏幕作为我的主窗口。

问题

起初,我认为这没什么大不了的,谷歌上有很多关于这个主题的博客和问题,但所有这些都不适用于我的项目。

我使用的是 MVVM 模式,意思是:

  • 不能在我的按钮上使用Mouse.GetPosition(ButtonName),因为 ViewModel 不知道它们的名字
  • 不能在点击事件中使用Mouse.GetPosition(sender),因为大多数按钮都使用命令。
  • 我显然也不能在我的视图代码中使用 PointToScreen,因为它会导致异常(此可视对象未连接到“PresentationSource”)
  • 可以在我的视图代码后面的 MouseMove-Event 上使用 Mouse.GetPosition(this) 并将其传递给我的 ViewModel,它将更新一个属性,我可以在创建时在我的命令中使用窗口,但我不喜欢必须永久更新属性的想法。如果没有 PointToScreen,我也无法设置与屏幕相关的点。
  • 不能使用任何 WinForms 引用,因为这会导致我当前项目中的冲突

  • 除了按钮之外,我还在我的 MainWindow 中托管了一个带有超链接的 UserControl,它打开了应该与超链接相关的其他窗口。

研究

  • here 的问题有很多不同的答案,但没有一个对我有用。

  • 由于我的 ViewModel 不知道 XAML 元素,因此我无法按照建议的here

  • 简单地通过点符号访问
  • 我的 ViewModel 不知道 WorkingArea,所以我什至无法让我的窗口出现在与 MainWindow 相同的屏幕上,如 here 所示
  • 与大多数其他答案一样,this one 似乎在 ViewModel 中不起作用

问题

我已经在一个问题上花费了相当长的时间,这个问题一开始似乎微不足道。由于到目前为止我查看的大多数问题似乎都是针对没有 MVVM 的窗口,那么在 ViewModel 中将窗口的位置设置为我的鼠标坐标或单击按钮的坐标的正确方法是什么?

编辑: 评论中要求的 MouseDownEvent: Xaml:

<Window x:Class="MySampleProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MySampleProject"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        MouseDown="Window_MouseDown">

C#:

private void Window_MouseDown(object sender, MouseEventArgs e)
{
    if (m_oDataContext != null)
    {
        m_oDataContext.MouseTest(Mouse.GetPosition(this));
    }
}

oDataContext 是我的 ViewModel。我的 MouseTest() 目前是空的。我确实在第一个括号处设置了一个断点。仅当在我的窗口中左键单击时才会到达断点,而不是在其托管控件之一中。

【问题讨论】:

  • 即使处理鼠标按下并将参数从鼠标按下事件传递给命令也没有任何问题。 MVVM 仍然有效
  • 我已经尝试过使用 MouseDown 事件,但它要么覆盖了我的控件的 MouseDown-Event,要么完全相反。然而,该事件并没有触发一半的时间。如何避免?
  • 请显示您对鼠标按下事件所做的代码
  • 我添加了所需的代码。我的控件没有 MouseDown 事件,但它们可能包含一些可能使用它的第三方 UI 元素。
  • 10 次中有 9 次您不需要按钮作为控件,如果您保持它们应有的状态,那么处理单击事件会容易得多。为什么你需要他们成为Controls

标签: c# wpf xaml mvvm


【解决方案1】:

下面是一个示例,说明如何将参数传递给 Vm 中的 Command:

窗口类:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext= new MyVm();
    }

    private void BtnWin1_OnClick(object sender, RoutedEventArgs e)
    {
        var dataContext = DataContext as MyVm;
        var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
        relativePoint.X += this.Left;
        relativePoint.Y += this.Top;
        dataContext?.OpenWindow1Command.Execute(relativePoint);
    }

    private void BtnWin2_OnClick(object sender, RoutedEventArgs e)
    {
        var dataContext = DataContext as MyVm;
        var relativePoint = ((Button)sender).TransformToAncestor(this).Transform(new Point(0, 0));
        relativePoint.X += this.Left;
        relativePoint.Y += this.Top;
        dataContext?.OpenWindow2Command.Execute(relativePoint);
    }
}

虚拟机类:

public class MyVm : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public ICommand OpenWindow1Command { get; }
    public ICommand OpenWindow2Command { get; }

    public MyVm()
    {
        OpenWindow1Command = new RelayCommand(OpenWindow1Command_Execute);
        OpenWindow2Command = new RelayCommand(OpenWindow2Command_Execute);
    }

    void OpenWindow1Command_Execute(object parameter)
    {
        var point = (Point)parameter;

        var win1 = new Window1{WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y};
        win1.Show();
    }

    void OpenWindow2Command_Execute(object parameter)
    {
        var point = (Point)parameter;

        var win2 = new Window2 { WindowStartupLocation = WindowStartupLocation.Manual, Left = point.X, Top = point.Y };
        win2.Show();
    }
} 

如果你还没有实现 Relay 类:

 public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<bool> _canExecute;

    public RelayCommand(Action<object> execute, Func<bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute.Invoke();
    }

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }
}

使用这种方法,您将失去 Command 的 CanExecute 功能,但会完成工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    相关资源
    最近更新 更多