【问题标题】:Handle MouseEnter over Commands in MVVM在 MVVM 中的命令上处理 MouseEnter
【发布时间】:2017-11-30 22:52:00
【问题描述】:

我有一个带有 ItemControl 的画布,它用自定义 UserControlViewModels 填充 ViewModel。我想给每个 UserControl 一个 MouseEnter 命令什么的。

我的 ViewModel 的 XAML 如下:

<Canvas x:Name="gameFieldCanvas" Width="{Binding CanvasWidth}" Height="{Binding CanvasHeight}">
   <ItemsControl ItemsSource="{Binding GameFieldContent}">
      <ItemsControl.ItemTemplate>
         <DataTemplate DataType="{x:Type local:GameFieldTileViewModel}">
             <Canvas>
                <local:GameFieldTileUserControl X="{Binding TileX}" Y="{Binding TileY}">
                   <local:GameFieldTileUserControl.InputBindings>
                      <MouseBinding MouseAction="LeftClick"
                                                  Command="{Binding DataContext.OnGameFieldTileLeftClicked, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                                  CommandParameter="{Binding}"/>
                      <MouseBinding MouseAction="RightClick" Command="{Binding DataContext.OnGameFieldTileRightClicked, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                                  CommandParameter="{Binding}"/>
                   </local:GameFieldTileUserControl.InputBindings>
                </local:GameFieldTileUserControl>
             </Canvas>
         </DataTemplate>
      </ItemsControl.ItemTemplate>
   </ItemsControl>
</Canvas>

如您所见,我已经有用于 Left-/RightClick 的命令,它工作正常。但是 MouseEnterEvents 没有 InputBindings。

我已经阅读了有关可以编写 EventTriggers 或类似内容的 Blend SDK 的信息,但是使用板工具是否可以实现?

我只是想实现当一个 UserControl 被输入时获取它的 UserControlViewModel。

【问题讨论】:

  • 什么意思?
  • 因为在 ViewModel 中处理 MouseEvents?我想操作 GameFieldContent 所以我需要画布的 ViewModel 中的命令。有没有更好的办法?
  • 我不知道你在做什么,所以我无法判断。我只能说这味道。您的 UI 应该只与您的 VM 传达您的用户意图,然后更新以响应 VM 状态的变化。您似乎正在将鼠标输入传输到正在解释意图的视图模型中。这应该在 UI 中完成。无论如何,在考虑您的设计时请记住这一点。祝你好运。
  • 我知道。理论和实践并不总是兼容的,至少我并不总是看路。问题是我的MapView 中有一个MapViewModel,包括用GameFieldViewModels 填充的画布,在视图中代表GameFieldTiles。当我希望在MapViewModel 中发生操作时,在每个GameFieldViewModel 中都有MouseEventHandler 有什么帮助。我的意思是不要误会我,我想要的只是“输入/单击了哪个 Tile 等”。知道我必须更改哪些模型。然后更改发生在调用PropertyChanged 方法的模型中。
  • 哇,听起来不错,真的!几周以来我是 wpf/c# 的新手,所以我不知道其中的技巧。而且我认为即使在您的描述之后,我也无法正确实施。也许您想发布一个带有更详细描述和一些 coden-ps 的答案?

标签: c# wpf xaml mvvm command


【解决方案1】:

您能否详细说明您最终想要完成的任务?我可能会为您提供更好的答案,但您可以,而且我有很多次覆盖现有控件并添加所需的命令属性。

这是一个具有鼠标移动命令的自定义用户控件。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WPF_Question_Answer_App
{
    public partial class MouseMoveCommandUserControl : UserControl
    {
        public MouseMoveCommandUserControl()
        {
            InitializeComponent();
            MouseMove += (s, e) =>
            {
                if (MouseMoveCommand?.CanExecute(MouseMoveCommandParameter) ?? false)
                    MouseMoveCommand.Execute(MouseMoveCommandParameter);
            };
        }

        public ICommand MouseMoveCommand
        {
            get => (ICommand)GetValue(MouseMoveCommandProperty);
            set => SetValue(MouseMoveCommandProperty, value);
        }

        public static readonly DependencyProperty MouseMoveCommandProperty =
            DependencyProperty.Register(nameof(MouseMoveCommand), typeof(ICommand), typeof(MouseMoveCommandUserControl), new PropertyMetadata(null));

        public object MouseMoveCommandParameter
        {
            get => GetValue(MouseMoveCommandParameterProperty);
            set => SetValue(MouseMoveCommandParameterProperty, value);
        }

        public static readonly DependencyProperty MouseMoveCommandParameterProperty =
            DependencyProperty.Register(nameof(MouseMoveCommandParameter), typeof(object), typeof(MouseMoveCommandUserControl), new PropertyMetadata(null));
    }
}

这里在视图中使用 in 很明显。

<Window x:Class="WPF_Question_Answer_App.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Question_Answer_App"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MouseMoveCommandUserControl MouseMoveCommand="{Binding SomeMouseMoveCommand}"
                                           MouseMoveCommandParameter="{Binding SomeMouseMoveCommandParameter}"/>
    </Grid>
</Window>

希望这会有所帮助...如果没有,我会删除答案,请告诉我。

【讨论】:

  • 感谢您提供代码。我必须编写一个应用程序来用 Tiles (GameFieldTileViewModel) 构建地图。初始地图将用GrassfieldTiles 填充。另外我有一个 WrapPanel,里面装满了可能的瓷砖(PlaseableObject)来放置。单击其中一个PlaseableObject 后,我会生成一个新的GameFieldTileViewModel 并将其添加到我的GameFieldContent 集合中。现在我想将它的 X/Y 坐标更改为我用鼠标输入的 GrassfieldTiles 的 X/Y 坐标,所以我想要一个 MouseEnterCommand,输入的 Tile 作为参数。
  • 如果 PlaseableObject 或 GrassfieldTiles 是您添加到 WrapPanel 的控件,并且您希望在这些控件上使用 MouseMoveCommand,那么只需将我在上面给出的 DependencyProperties 复制并粘贴到这些控件中。然后在构造函数中添加 MouseMove 事件以尝试触发命令,最后将命令绑定到控件,就像您绑定任何其他普通命令一样。当鼠标在控件中移动时,他的命令将触发(或测试触发)。
  • 是的,但我怎样才能将GameFieldTileViewModel 绑定到MouseMoveCommandParameter?当我像在MouseBinding 中那样做时,我会在输入磁贴时在if (MouseMoveCommand.CanExecute(MouseMoveCommandParameter)) 得到NullReferenceException
  • 哦停下!现在我有了!我刚刚忘记了什么!谢谢!你保护了我的一天! =)
  • 你好!我设法为MouseWheel 复制了您的代码,我需要如何自定义该代码以通过命令获取MouseWheel 的方向?
【解决方案2】:

只是一个如何使用内置组件来完成它的示例..

您可以利用已经存在的 UI 组件来实现您的大部分目标。有副作用(不能连续两次点击同一个图块,需要修改选择的行为方式等),但这是另一种处理方式。

在您的窗口中,使用带有适当面板的 ListBox。在我的示例中,您可以将此作为井字游戏的基础:

<Window x:Class="TileClicker.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:TileClicker"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <ListBox
        ItemsSource="{Binding Tiles}"
        SelectedItem="{Binding SelectedTile}" DisplayMemberPath="Name">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid
                    Rows="3"
                    Columns="3" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Window>

窗口的代码隐藏最少

public MainWindow()
{
    InitializeComponent();
    DataContext = new ViewModel();
}

我们的 tile 由

表示
public class Tile : Notifier
{
    private string _name = "Click me";

    public string Name
    {
        get
        {
            return _name;
        }
        set { _name = value; Notify(); }
    }
}

我们的视图模型使用 SelectedItem 绑定来知道何时单击了图块,并做出适当的响应

public class ViewModel : Notifier
{
    private Tile _tile;
    public ObservableCollection<Tile> Tiles { get; } = new ObservableCollection<Tile> {
        new Tile(), new Tile(), new Tile(), 
        new Tile(), new Tile(), new Tile(), 
        new Tile(), new Tile(), new Tile()
    };
    public Tile SelectedTile
    {
        get
        {
            return _tile;
        }
        set
        {
            _tile = value;
            Notify();
            UpdateName(value);
        }
    }

    private void UpdateName(Tile value)
    {
        if(value.Name == "Click me")
            value.Name = "Woot!";
        else
            value.Name = "Click me";
    }
}

基础只是从上述类中删除了 INPC impl 以方便阅读

public class Notifier : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void Notify([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

【讨论】:

  • 我觉得这看起来不错。但是该 ListBox 控件是否具有与画布一样的可能性,添加各种具有显式 X 和 Y 坐标的用户控件等?通常 ListControls 是否具有某种元素格式?我明天试试!
  • @kuemme01 您可以使用 Canvas 而不是 UniformGrid 作为面板,并且可以配置 ItemTemplate 以将每个模型的 X 和 Y 坐标绑定到 Canvas.X 和 Canvas.Y。我以前做过,而且效果很好。
猜你喜欢
  • 2011-06-28
  • 1970-01-01
  • 2011-10-05
  • 2013-04-26
  • 2010-10-16
  • 2013-02-21
  • 2012-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多