You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

  1. 在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);
    }
    
  2. 修改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"/>
    
  3. 在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接口抽象坐标获取能力:

  1. 定义IView接口:
    public interface IJoystickView
    {
        (double X, double Y) GetRelativePointerPosition(PointerRoutedEventArgs args);
    }
    
  2. 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);
        }
    }
    
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:35:13