From MVVM Design pattern, the viewmodel should not know the view. But in my case, I need the view and the model, I mean :
从MVVM设计模式来看,viewmodel不应该知道视图。但就我而言,我需要视图和模型,我的意思是:
In my window, I've an Image component. I'd like to get mouse position when mouse moves over the Image component and save it into my model.
在我的窗口中,我有一个Image组件。当鼠标移过Image组件并将其保存到我的模型中时,我想获得鼠标位置。
The code behind would have been :
背后的代码应该是:
void Foo_MouseMove(objet sender, MouseEventArgs e)
{
model.x = e.getPosition(this.imageBox).X;
model.y = e.getPosition(this.imageBox).Y;
}
The problem is : I need this.imageBox and MouseEventArgs, so two View element.
问题是:我需要this.imageBox和MouseEventArgs,所以两个View元素。
My question is : How to deal with this case using the MVVM approach ?
我的问题是:如何使用MVVM方法处理这种情况?
I use MVVM light framework
我使用MVVM轻量级框架
3 个解决方案
#1
6
Finnally found an answer, using a EventConverter :
Finnally使用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;
}
}
This converter allows me to deal with Point and not with graphics components.
这个转换器允许我处理Point而不是图形组件。
Here goes the XML :
这是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>
#2
6
I would use an attached behaviour here. This will allow you to continuously monitor the mouse position, rather than simply responding to an event such as MouseDown. You'll need to add a reference to the System.Windows.Interactivity
assembly.
我会在这里使用附加行为。这将允许您连续监视鼠标位置,而不是简单地响应MouseDown等事件。您需要添加对System.Windows.Interactivity程序集的引用。
The code below provides a simple example of this in action.
下面的代码提供了一个简单的示例。
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>
Note that, in the above XAML, the MouseBehaviour is pushing the mouse position down to the ViewModel through a OneWayToSource binding, while the two TextBlocks are reading the mouse positions from the ViewModel.
请注意,在上面的XAML中,MouseBehaviour通过OneWayToSource绑定将鼠标位置向下推到ViewModel,而两个TextBlock正在从ViewModel读取鼠标位置。
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));
}
}
Attached Behaviour
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;
}
}
#3
3
Mark Greens solution is the best (I found).
Mark Greens解决方案是最好的(我发现)。
If you want to make his solution reusable for any WPF control (which I suggest), inheriting from System.Windows.Interactivity.Behavior<Control>
actually won't work for Panel
, because Panel
does not inherit from Control
. Only those classes inherit from Control
: https://msdn.microsoft.com/de-de/library/system.windows.controls.control(v=vs.110).aspx
如果你想让他的解决方案可以重用任何WPF控件(我建议),继承System.Windows.Interactivity.Behavior
Instead, inherit from System.Windows.Interactivity.Behavior<FrameworkElement>
. FrameworkElement
is the ancestor of all WPF control classes: https://msdn.microsoft.com/de-de/library/system.windows.frameworkelement(v=vs.110).aspx. I have tested it on Grid
, Panel
and Image
btw.
相反,继承自System.Windows.Interactivity.Behavior
I use it to keep a Popup in sync with the mouse cursor:
我使用它来保持Popup与鼠标光标同步:
<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.: I would have commented on the solution, but my answer is too long.
P.S。:我会对解决方案发表评论,但我的答案太长了。
#1
6
Finnally found an answer, using a EventConverter :
Finnally使用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;
}
}
This converter allows me to deal with Point and not with graphics components.
这个转换器允许我处理Point而不是图形组件。
Here goes the XML :
这是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>
#2
6
I would use an attached behaviour here. This will allow you to continuously monitor the mouse position, rather than simply responding to an event such as MouseDown. You'll need to add a reference to the System.Windows.Interactivity
assembly.
我会在这里使用附加行为。这将允许您连续监视鼠标位置,而不是简单地响应MouseDown等事件。您需要添加对System.Windows.Interactivity程序集的引用。
The code below provides a simple example of this in action.
下面的代码提供了一个简单的示例。
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>
Note that, in the above XAML, the MouseBehaviour is pushing the mouse position down to the ViewModel through a OneWayToSource binding, while the two TextBlocks are reading the mouse positions from the ViewModel.
请注意,在上面的XAML中,MouseBehaviour通过OneWayToSource绑定将鼠标位置向下推到ViewModel,而两个TextBlock正在从ViewModel读取鼠标位置。
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));
}
}
Attached Behaviour
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;
}
}
#3
3
Mark Greens solution is the best (I found).
Mark Greens解决方案是最好的(我发现)。
If you want to make his solution reusable for any WPF control (which I suggest), inheriting from System.Windows.Interactivity.Behavior<Control>
actually won't work for Panel
, because Panel
does not inherit from Control
. Only those classes inherit from Control
: https://msdn.microsoft.com/de-de/library/system.windows.controls.control(v=vs.110).aspx
如果你想让他的解决方案可以重用任何WPF控件(我建议),继承System.Windows.Interactivity.Behavior
Instead, inherit from System.Windows.Interactivity.Behavior<FrameworkElement>
. FrameworkElement
is the ancestor of all WPF control classes: https://msdn.microsoft.com/de-de/library/system.windows.frameworkelement(v=vs.110).aspx. I have tested it on Grid
, Panel
and Image
btw.
相反,继承自System.Windows.Interactivity.Behavior
I use it to keep a Popup in sync with the mouse cursor:
我使用它来保持Popup与鼠标光标同步:
<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.: I would have commented on the solution, but my answer is too long.
P.S。:我会对解决方案发表评论,但我的答案太长了。