WinUI 3 MVPVM架构下摇杆控件Presenter引用UI元素问题求解
问题
我正在将传统代码后置的摇杆UserControl重写为MVPVM架构的WinUI 3控件,目前Presenter类直接引用UIElement(ControlArea)违反了架构原则,需要改写OnPointerMoved方法里获取坐标的两行代码:
Double x = eventArgs.GetCurrentPoint(ControlArea).Position.X - ViewModel.ControlRadius; Double y = eventArgs.GetCurrentPoint(ControlArea).Position.Y - ViewModel.ControlRadius;
现有View的XAML代码:
<Ellipse x:Name="ControlArea" Height="{x:Bind p.ViewModel.OuterDiameter}" Width="{x:Bind p.ViewModel.OuterDiameter}" Fill="{x:Bind p.ViewModel.OuterFill}" Stroke="{x:Bind p.ViewModel.OuterStroke}"/>
Presenter类的OnPointerMoved方法代码:
void OnPointerMoved(object sender, PointerRoutedEventArgs eventArgs) { if (!ViewModel.ControllerPressed) return; Double x = eventArgs.GetCurrentPoint(ControlArea).Position.X - ViewModel.ControlRadius; Double y = eventArgs.GetCurrentPoint(ControlArea).Position.Y - ViewModel.ControlRadius; double disp = Math.Sqrt(x * x + y * y); if (disp < ViewModel.ControlRadius) { ViewModel.JoystickTransform.X = ViewModel.XValue = x; ViewModel.JoystickTransform.Y = ViewModel.YValue = y; } else { ViewModel.JoystickTransform.X = ViewModel.XValue = ViewModel.ControlRadius * (x / disp); //A*cos(x) ViewModel.JoystickTransform.Y = ViewModel.YValue = ViewModel.ControlRadius * (y / disp); //A*sin(x) } ViewModel.OnJoyStickMoved?.Invoke(sender, new EventArgs()); }
解决方案
核心思路是让View负责UI相关的坐标转换,Presenter仅处理业务逻辑,不直接依赖UI元素。
方案一:View预处理坐标后传递给Presenter
- 在View的代码后置中绑定指针移动事件,计算相对中心的坐标后调用Presenter的处理方法:
private void ControlArea_PointerMoved(object sender, PointerRoutedEventArgs e) { if (!p.ViewModel.ControllerPressed) return; var controlArea = sender as Ellipse; var point = e.GetCurrentPoint(controlArea); double x = point.Position.X - p.ViewModel.ControlRadius; double y = point.Position.Y - p.ViewModel.ControlRadius; p.HandleJoystickMovement(x, y); } - 修改XAML,将事件绑定到View的代码后置方法:
<Ellipse x:Name="ControlArea" Height="{x:Bind p.ViewModel.OuterDiameter}" Width="{x:Bind p.ViewModel.OuterDiameter}" Fill="{x:Bind p.ViewModel.OuterFill}" Stroke="{x:Bind p.ViewModel.OuterStroke}" PointerMoved="ControlArea_PointerMoved"/> - 在Presenter中新增处理方法,接收预处理后的坐标:
public void HandleJoystickMovement(double x, double y) { double disp = Math.Sqrt(x * x + y * y); if (disp < ViewModel.ControlRadius) { ViewModel.JoystickTransform.X = ViewModel.XValue = x; ViewModel.JoystickTransform.Y = ViewModel.YValue = y; } else { ViewModel.JoystickTransform.X = ViewModel.XValue = ViewModel.ControlRadius * (x / disp); ViewModel.JoystickTransform.Y = ViewModel.YValue = ViewModel.ControlRadius * (y / disp); } ViewModel.OnJoyStickMoved?.Invoke(this, new EventArgs()); }
方案二:通过抽象接口解耦Presenter与View
如果需要更彻底的解耦,可定义IView接口抽象坐标获取能力:
- 定义IView接口:
public interface IJoystickView { (double X, double Y) GetRelativePointerPosition(PointerRoutedEventArgs args); } - View实现该接口:
public sealed partial class JoystickControl : UserControl, IJoystickView { public JoystickControl() { InitializeComponent(); } public (double X, double Y) GetRelativePointerPosition(PointerRoutedEventArgs args) { var point = args.GetCurrentPoint(ControlArea); double x = point.Position.X - p.ViewModel.ControlRadius; double y = point.Position.Y - p.ViewModel.ControlRadius; return (x, y); } } - Presenter依赖IView接口,修改
OnPointerMoved方法:private readonly IJoystickView _view; public JoystickPresenter(IJoystickView view, JoystickViewModel viewModel) { _view = view; ViewModel = viewModel; } void OnPointerMoved(object sender, PointerRoutedEventArgs eventArgs) { if (!ViewModel.ControllerPressed) return; var (x, y) = _view.GetRelativePointerPosition(eventArgs); double disp = Math.Sqrt(x * x + y * y); if (disp < ViewModel.ControlRadius) { ViewModel.JoystickTransform.X = ViewModel.XValue = x; ViewModel.JoystickTransform.Y = ViewModel.YValue = y; } else { ViewModel.JoystickTransform.X = ViewModel.XValue = ViewModel.ControlRadius * (x / disp); ViewModel.JoystickTransform.Y = ViewModel.YValue = ViewModel.ControlRadius * (y / disp); } ViewModel.OnJoyStickMoved?.Invoke(sender, new EventArgs()); }
内容的提问来源于stack exchange,提问作者Keyboard_Kowboy
相关产品推荐
相关产品推荐

