【问题标题】:How do I get mouse positions in my view model如何在我的视图模型中获取鼠标位置
【发布时间】:2015-07-14 20:37:04
【问题描述】:

从 MVVM 设计模式来看,视图模型不应该知道视图。但就我而言,我需要视图和模型,我的意思是:

在我的窗口中,我有一个 Image 组件。当鼠标移到 Image 组件上时,我想获得鼠标位置并将其保存到我的模型中。

后面的代码是:

void Foo_MouseMove(objet sender, MouseEventArgs e)
{
  model.x = e.getPosition(this.imageBox).X; 
  model.y = e.getPosition(this.imageBox).Y;
}

问题是:我需要this.imageBox和MouseEventArgs,所以需要两个View元素。

我的问题是:如何使用 MVVM 方法处理这种情况?

我使用 MVVM 轻框架

【问题讨论】:

  • 我需要this.imageBox和MouseEventArgs,所以两个View元素...如果是view元素,那么在后面的view代码中处理,不管你是否'是否重新使用 MVVM。视图元素属于视图模型。

标签: c# wpf mvvm


【解决方案1】:

我会在这里使用附加行为。这将允许您持续监视鼠标位置,而不是简单地响应诸如 MouseDown 之类的事件。您需要添加对 System.Windows.Interactivity 程序集的引用。

下面的代码提供了一个简单的例子。

XAML

<Window x:Class="MouseMoveMvvm.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:mouseMoveMvvm="clr-namespace:MouseMoveMvvm"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DockPanel>
            <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
                <TextBlock Text="{Binding PanelX, StringFormat='X={0}'}" />
                <TextBlock Text="{Binding PanelY, StringFormat='y={0}'}" />
            </StackPanel>
            <Canvas DockPanel.Dock="Bottom" Background="Aqua">
                <i:Interaction.Behaviors>
                    <mouseMoveMvvm:MouseBehaviour MouseX="{Binding PanelX, Mode=OneWayToSource}" MouseY="{Binding PanelY, Mode=OneWayToSource}" />
                </i:Interaction.Behaviors>
            </Canvas>
        </DockPanel>
    </Grid>
</Window>

请注意,在上述 XAML 中,MouseBehaviour 通过 OneWayToSource 绑定将鼠标位置向下推到 ViewModel,而两个 TextBlock 正在从 ViewModel 读取鼠标位置。

视图模型

public class MainWindowViewModel : INotifyPropertyChanged
{
    private double _panelX;
    private double _panelY;
    public event PropertyChangedEventHandler PropertyChanged;

    public double PanelX
    {
        get { return _panelX; }
        set
        {
            if (value.Equals(_panelX)) return;
            _panelX = value;
            OnPropertyChanged();
        }
    }

    public double PanelY
    {
        get { return _panelY; }
        set
        {
            if (value.Equals(_panelY)) return;
            _panelY = value;
            OnPropertyChanged();
        }
    }


    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

附加行为

public class MouseBehaviour : System.Windows.Interactivity.Behavior<FrameworkElement>
{
    public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
        "MouseY", typeof (double), typeof (MouseBehaviour), new PropertyMetadata(default(double)));

    public double MouseY
    {
        get { return (double) GetValue(MouseYProperty); }
        set { SetValue(MouseYProperty, value); }
    }

    public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
        "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));

    public double MouseX
    {
        get { return (double) GetValue(MouseXProperty); }
        set { SetValue(MouseXProperty, value); }
    }

    protected override void OnAttached()
    {
        AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
    }

    private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
    {
        var pos = mouseEventArgs.GetPosition(AssociatedObject);
        MouseX = pos.X;
        MouseY = pos.Y;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
    }
}

【讨论】:

  • 这应该是公认的答案,对我来说就像一种享受。我说得对吗?它不一定是画布控件,它可以是您希望监视其坐标的任何控件,甚至是按钮,只要在附加的行为类中指定即可?
  • 当然。我编写的代码应该具有继承自 System.Windows.Interactivity.Behavior 而不是 System.Windows.Interactivity.Behavior 的行为,因为 MouseMove 是 Control 的属性,但我将 Panel 留在我的回答,因为它与问题更密切相关。
【解决方案2】:

终于找到了答案,使用 EventConverter :

  public class MouseButtonEventArgsToPointConverter : IEventArgsConverter
    {
        public object Convert(object value, object parameter)
        {
            var args = (MouseEventArgs)value;
            var element = (FrameworkElement)parameter;
            var point = args.GetPosition(element);
            return point;
        }
    }

这个转换器允许我处理点而不是图形组件。

这里是 XML:

        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseMove">
                <cmd:EventToCommand
                 Command="{Binding Main.MouseMoveCommand, Mode=OneWay}"
                 EventArgsConverter="{StaticResource MouseButtonEventArgsToPointConverter}"
                 EventArgsConverterParameter="{Binding ElementName=Image1}"
                 PassEventArgsToCommand="True" />
            </i:EventTrigger>
        </i:Interaction.Triggers>

【讨论】:

  • 您能分享整个 XAML 吗?什么是cmd?我是什么?
  • 3年多了,不知道
  • 我认为是:xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"this link
  • @Gabriel,我认为“i”是这样的交互命名空间:xmlns:i="schemas.microsoft.com/expression/2010/interactivity" 实际上,i 是我们为要在 Xaml 中使用的命名空间选择的名称。正如 BugsFixer 所说,cmd 是用于 MVVM Light 命名空间的命名空间。
【解决方案3】:

Mark Greens 解决方案是最好的(我发现)。

如果你想让他的解决方案可重用于任何 WPF 控件(我建议),从 System.Windows.Interactivity.Behavior&lt;Control&gt; 继承实际上不适用于 Panel,因为 Panel 不从 Control 继承。 只有那些类继承自Controlhttps://msdn.microsoft.com/de-de/library/system.windows.controls.control(v=vs.110).aspx

而是从System.Windows.Interactivity.Behavior&lt;FrameworkElement&gt; 继承。 FrameworkElement 是所有 WPF 控件类的祖先:https://msdn.microsoft.com/de-de/library/system.windows.frameworkelement(v=vs.110).aspx。 我已经在GridPanelImage btw 上对其进行了测试。

我用它来使弹出窗口与鼠标光标保持同步:

<Image x:Name="Image1">        
  <i:Interaction.Behaviors>
    <myNamespace:MouseBehaviour
      MouseX="{Binding ElementName=Popup1, Path=HorizontalOffset, Mode=OneWayToSource}"
      MouseY="{Binding ElementName=Popup1, Path=VerticalOffset, Mode=OneWayToSource}">
    </myNamespace:MouseBehaviour>
  </i:Interaction.Behaviors>
</Image>

<Popup x:Name="Popup1" PlacementTarget="{Binding ElementName=Image1}"/>

P.S.:我本来想对解决方案发表评论,但我的回答太长了。

【讨论】:

  • 你说得对。从 FrameworkElement 继承要好得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多