【问题标题】:How to properly filter the different actions that may result from a mouse event?如何正确过滤鼠标事件可能导致的不同操作?
【发布时间】:2017-05-04 16:01:58
【问题描述】:

我正在设计一个 CAD 应用程序,但不确定如何以简洁的方式设计鼠标事件的事件处理。

为简单起见,假设我在 UI 中有两个按钮:CreateSquare 和 CreateCircle。当用户单击其中一个时,我希望应用程序等待画布中的单击,并在用户单击后在标识的位置创建正方形或圆形。

据我了解,有必要在我的画布中监听 MouseDown 事件,并在处理程序中编写如下内容:

bool CreatingSquaresMode;
bool CreatingCirclesMode;

private void ModelViewport_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (CreatingSquaresMode) { 
        // create square at selected position. 
    }
    if (CreatingCirclesMode) { 
        // create circle at selected position. 
    }
}

但是这看起来很臭,特别是因为我会有许多不同的创建命令,而且很快就会失控。它还破坏了 MVVM,因为我希望将 CreateSquare 和 CreateCircle 按钮绑定到命令,而不必担心 MouseDown 事件。

我考虑过的一个替代方案是状态机,我将在其中确定应用程序的所有可能模式,并以更优雅的方式复制上述 if-nest 或 switch-case。仍然感觉这不是正确的解决方案,但会更好。

第二种选择——这是我认为正确的选择——将在我的 CreateSquare 命令中以某种方式监听 MouseDown 事件:

   private void CreateSquare() {
       //do something here
       //wait for user mouse input
       //create square
   }

但我不知道这是否可能。

处理这种情况的正确方法是什么?我确信这里有一种设计模式应该可以帮助我。

【问题讨论】:

  • 它被标记为 MVVM,因此您应该为视图模型中的每个按钮都有一个 ICommand,然后它会记住用户正在做什么(创建形状 x)。当用户再次单击画布时,您应该再次拥有一个发送单击坐标的 ICommand。我会在管理这个的 UserControl 中有画布,所以它不必担心视图模型在做什么。然后,视图模型可以通过在其形状集合中的给定位置添加正确的形状来做出响应。
  • 我最终做了什么。好眼@Will!

标签: c# wpf mvvm mouseevent cad


【解决方案1】:

有许多不同的方法可以解决您的问题,因此没有“正确”的方法,但肯定有更好的方法。我提供了一个供您考虑,但我永远不能说我(或其他任何人)的设计是“最好的”,这由您决定。

在您的解决方案中,您使用第一个按钮来设置名为“CreatingShapeMode”的属性。您是否只想在任何特定时间设置一种模式?如果是这样考虑使用enum

在我看来,您的 MouseDown 事件处理程序不应执行绘制形状的工作(正如您指出的那样,此列表只会变得更大)。相反,我会考虑使用一些都继承自基本“ObjectCreator”类型的自定义形状绘图类。您的第一个按钮单击将设置正确的类型,例如圆形或方形;第二次点击画布(这会触发你的鼠标按下事件)只会调用 createShape 方法;它不需要任何 if 语句,因为它是知道如何绘制形状的对象创建者。

这是一个控制台应用程序,它使用最少的代码来演示我要说的内容。显然,它需要大量的适应才能适应您的问题。

using System;

namespace ConsoleApplication27
{
    class Program
    {
        private static ObjectCreator creator;
        static void Main(string[] args)
        {
            creator = new CircleCreator();//change this to change the "shape" you get

            Console.ReadLine();
        }

        private static void YourMouseDownMethod()
        {
            creator.CreateObject();//no if statements required, the createObject method does the work
        }
    }



    abstract class ObjectCreator
    {
        public abstract void CreateObject();
    }

    class SquareCreator:ObjectCreator
    {
        public override void CreateObject()
        {
            Console.WriteLine("Create Square");
        }
    }

    class CircleCreator:ObjectCreator
    {
        public override void CreateObject()
        {
            Console.WriteLine("Create Circle");
        }
    }
}

您提到您希望按钮绑定到命令,您可以使用命令对象来设置正确的 ObjectCreator 类型。

    class SetObjectCreatorCommand:ICommand 
    {
        private readonly ObjectCreator _creator;

        public SetObjectCreatorCommand(ObjectCreator creator)
        {
            _creator = creator;//for each shape type you would create an instance of this class
            //e.g. pass in a circleCreator for circle shapes etc...
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            //Set the creator class here, you can bind a button's command property to a Command object
        }

        public event EventHandler CanExecuteChanged;
    }

【讨论】:

  • 感谢您的回答,它为我指明了正确的方向。请参阅下面的答案以了解我最终是如何做到的。
【解决方案2】:

对于那些阅读,这就是我解决这个问题的方法。上面的答案帮助证实了我的想法。

首先,我创建了一个具有以下状态和触发器的状态机(我使用了Stateless):

public enum States
{
    Idle,
    CreatingShape
}

public enum Triggers
{
    CreateShape,
    CancelCurrentAction
}

public class AppStateMachine : ObservableObject
{
    public StateMachine<States, Triggers> _stateMachine = new StateMachine<States, Triggers>(States.Idle);

    public AppStateMachine()
    {
        _stateMachine.Configure(States.Idle)
            .Permit(Triggers.CreateShape, States.CreatingJunction)
            .Ignore(Triggers.CancelCurrentAction);

        _stateMachine.Configure(States.CreatingShape)
            .Permit(Triggers.CancelCurrentAction, States.Idle)
            .Ignore(Triggers.CreateShape);
    }

然后,为了尽可能地保留 MVVM,我将 UI 按钮绑定到“SetCreationMode”命令,命令参数是要创建的形状类型。命令是这样的:

XAML:

<RibbonButton x:Name="CreateSquare" Command="{Binding SetShapeCreationCommand}" CommandParameter="{Binding SquareShape}" Label="Create Square"/>

视图模型:

    private void SetShapeCreationMode(ShapeTypeEnum ShapeType)
    {
        SelectedShapeType = ShapeType;
        _mainViewModel.AppState._stateMachine.Fire(Triggers.CreateShape);
    }

这样,当您按下按钮时,应用状态机触发“CreatingShape”状态,并且“SelectedShapeType”变量设置为您的形状(在示例中为正方形)。

之后,您仍然必须将画布点击作为事件处理(我正在使用 MVVMLight 的 EventToCommand,但还没有实现飞跃。)事件处理方法类似于:

private void ModelViewport_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (_mainViewModel.AppState.IsInState(States.CreatingShape))
        {
            // retrieve mouse position

            // Execute command with retrieved position
            _CreateShapeViewModel.CreateShapeGraphicallyCommand.Execute(Position);
        }
    }

这样您就可以在选定的鼠标位置上使用该命令,而无需在 EventHandler 中使用大量代码,而是在 ViewModel 中。

这是最好的处理方式吗?我不知道,但我希望它会有所帮助。

【讨论】:

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