如何正确过滤鼠标事件可能导致的不同操作?
How to properly filter the different actions that may result from a mouse event?
我正在设计 CAD 应用程序,但不确定如何以干净的方式设计鼠标事件的事件处理。
为简单起见,假设我在 UI 中有两个按钮:CreateSquare 和 CreateCircle。当用户单击其中一个时,我希望应用程序等待 canvas 中的单击,并在用户单击后在指定位置创建正方形或圆形。
据我了解,有必要在我的 canvas 中监听 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
}
但我不知道这是否可能。
处理这种情况的正确方法是什么?我确定有一种设计模式应该可以帮到我。
有许多不同的方法可以解决您的问题,因此没有 "correct" 方法,但是肯定有更好的方法。我提供了一个供您考虑,但我永远不能说我(或其他任何人)的设计是 "best",这由您决定。
在您的解决方案中,您使用第一次按下按钮来设置名为 "CreatingShapeMode" 的属性。您是否只想在任何特定时间设置一种模式?如果是这样,请考虑使用 enum
在我看来,您的 MouseDown 事件处理程序不应该执行绘制形状的工作(因为您指出此列表只会变得更大)。相反,我会考虑使用一些自定义形状绘图 类,它们都是从基础 "ObjectCreator" 类型继承的。您的第一个按钮点击将设置正确的类型,例如圆形或方形;第二次单击 canvas(这会触发鼠标按下事件)只会调用 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;
}
对于那些正在阅读的人,这就是我解决这个问题的方法。上面的回答帮助证实了我的想法。
首先,我创建了一个具有以下状态和触发器的状态机(我使用了 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);
}
这样,当您按下按钮时,App 状态机会触发 "CreatingShape" 状态,并且 "SelectedShapeType" 变量会设置为您的形状(在示例中为正方形)。
之后,您仍然需要将 canvas 单击作为事件处理(我正在尝试使用 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 中使用大量代码,而 EventHandler 位于 ViewModel 中。
这是最好的处理方式吗?不知道,希望对你有帮助。
我正在设计 CAD 应用程序,但不确定如何以干净的方式设计鼠标事件的事件处理。
为简单起见,假设我在 UI 中有两个按钮:CreateSquare 和 CreateCircle。当用户单击其中一个时,我希望应用程序等待 canvas 中的单击,并在用户单击后在指定位置创建正方形或圆形。
据我了解,有必要在我的 canvas 中监听 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
}
但我不知道这是否可能。
处理这种情况的正确方法是什么?我确定有一种设计模式应该可以帮到我。
有许多不同的方法可以解决您的问题,因此没有 "correct" 方法,但是肯定有更好的方法。我提供了一个供您考虑,但我永远不能说我(或其他任何人)的设计是 "best",这由您决定。
在您的解决方案中,您使用第一次按下按钮来设置名为 "CreatingShapeMode" 的属性。您是否只想在任何特定时间设置一种模式?如果是这样,请考虑使用 enum
在我看来,您的 MouseDown 事件处理程序不应该执行绘制形状的工作(因为您指出此列表只会变得更大)。相反,我会考虑使用一些自定义形状绘图 类,它们都是从基础 "ObjectCreator" 类型继承的。您的第一个按钮点击将设置正确的类型,例如圆形或方形;第二次单击 canvas(这会触发鼠标按下事件)只会调用 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;
}
对于那些正在阅读的人,这就是我解决这个问题的方法。上面的回答帮助证实了我的想法。
首先,我创建了一个具有以下状态和触发器的状态机(我使用了 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);
}
这样,当您按下按钮时,App 状态机会触发 "CreatingShape" 状态,并且 "SelectedShapeType" 变量会设置为您的形状(在示例中为正方形)。
之后,您仍然需要将 canvas 单击作为事件处理(我正在尝试使用 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 中使用大量代码,而 EventHandler 位于 ViewModel 中。
这是最好的处理方式吗?不知道,希望对你有帮助。