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

WinUI+Win2D自定义CanvasControl的MVVM双向绑定方案咨询

问题分析与解决方案

你的思路评估

思路1:创建ObservableCollection

核心方向正确,但存在认知误区:

  • 自定义业务控件绑定ViewModel是合理的:通用控件(如TextBox)绑定基础类型是因为它们是通用组件,而你的Canvas是业务专属控件,绑定业务ViewModel完全符合MVVM分层原则。
  • 手动创建VM不规范的问题可解决:通过DI注入ViewModel工厂,而非直接new,就能遵循依赖注入规范。

思路2:视图层PolygonItem+值转换器

此方案不可行:

  • 视图层直接引用Model(Polygon)违反MVVM分层规则,视图应仅与ViewModel交互,不能直接依赖数据模型。
  • 值转换器的跨层访问问题无法规避,无论放在哪一层都会打破层级隔离。

符合严格MVVM的实现方案

1. 定义PolygonViewModel(ViewModel层)

封装Polygon模型,实现INotifyPropertyChanged(借助CommunityToolkit.Mvvm的ObservableObject简化),负责视图与模型的数据同步:

public partial class PolygonViewModel : ObservableObject
{
    private readonly Polygon _model;

    public PolygonViewModel(Polygon model)
    {
        _model = model;
        Name = model.Name;
        // 将模型的只读列表转为ObservableCollection,支持双向绑定
        Points = new ObservableCollection<Vector2>(model.Points);
        Cutouts = new ObservableCollection<ObservableCollection<Vector2>>(
            model.Cutouts.Select(c => new ObservableCollection<Vector2>(c)));
        
        // 监听集合变化,同步回模型
        Points.CollectionChanged += SyncPointsToModel;
        Cutouts.CollectionChanged += SyncCutoutsToModel;
    }

    [ObservableProperty]
    private string _name;

    public ObservableCollection<Vector2> Points { get; }
    public ObservableCollection<ObservableCollection<Vector2>> Cutouts { get; }

    // 封装模型的业务操作
    public void Rotate(double degrees)
    {
        _model.Rotate(degrees);
        // 同步ViewModel的Points(根据Rotate逻辑更新)
        UpdatePointsFromModel();
        InvalidateCanvas(); // 通知画布重绘
    }

    private void SyncPointsToModel(object? sender, NotifyCollectionChangedEventArgs e)
    {
        _model.Points.Clear();
        foreach (var point in Points) _model.Points.Add(point);
    }

    private void SyncCutoutsToModel(object? sender, NotifyCollectionChangedEventArgs e)
    {
        _model.Cutouts.Clear();
        foreach (var cutout in Cutouts) _model.Cutouts.Add(cutout.ToList());
    }

    private void UpdatePointsFromModel()
    {
        Points.Clear();
        foreach (var point in _model.Points) Points.Add(point);
    }

    private void InvalidateCanvas()
    {
        PolygonChanged?.Invoke(this, EventArgs.Empty);
    }

    public event EventHandler? PolygonChanged;
}

2. 页面ViewModel中通过DI构建ViewModel集合

使用DI注入PolygonViewModel的工厂,避免手动实例化,符合依赖注入规范:

public partial class MainViewModel : ObservableObject
{
    private readonly Func<Polygon, PolygonViewModel> _polygonVmFactory;

    [ObservableProperty]
    private ObservableCollection<PolygonViewModel> _polygonViewModels;

    public MainViewModel(Func<Polygon, PolygonViewModel> polygonVmFactory)
    {
        _polygonVmFactory = polygonVmFactory;
        // 从数据源获取模型集合,转换为ViewModel集合
        var polygons = GetPolygonsFromDataSource();
        PolygonViewModels = new ObservableCollection<PolygonViewModel>(
            polygons.Select(p => _polygonVmFactory(p)));
    }

    private List<Polygon> GetPolygonsFromDataSource()
    {
        // 模拟从数据源获取Polygon模型
        return new List<Polygon>();
    }
}

3. 自定义CanvasControl绑定ViewModel集合(视图层)

画布仅与PolygonViewModel交互,不直接引用模型,处理渲染和拖拽操作:

public class CustomCanvasControl : CanvasControl
{
    public static readonly DependencyProperty PolygonViewModelsProperty =
        DependencyProperty.Register(
            nameof(PolygonViewModels),
            typeof(ObservableCollection<PolygonViewModel>),
            typeof(CustomCanvasControl),
            new PropertyMetadata(null, OnPolygonViewModelsChanged));

    public ObservableCollection<PolygonViewModel> PolygonViewModels
    {
        get => (ObservableCollection<PolygonViewModel>)GetValue(PolygonViewModelsProperty);
        set => SetValue(PolygonViewModelsProperty, value);
    }

    private static void OnPolygonViewModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var canvas = (CustomCanvasControl)d;
        if (e.OldValue is ObservableCollection<PolygonViewModel> oldCol)
        {
            oldCol.CollectionChanged -= canvas.OnCollectionChanged;
            foreach (var vm in oldCol) vm.PolygonChanged -= canvas.OnPolygonChanged;
        }
        if (e.NewValue is ObservableCollection<PolygonViewModel> newCol)
        {
            newCol.CollectionChanged += canvas.OnCollectionChanged;
            foreach (var vm in newCol) vm.PolygonChanged += canvas.OnPolygonChanged;
            canvas.Invalidate();
        }
    }

    private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        Invalidate();
    }

    private void OnPolygonChanged(object? sender, EventArgs e)
    {
        Invalidate();
    }

    protected override void Draw(CanvasDrawingSession session, Rect actualBounds)
    {
        base.Draw(session, actualBounds);
        if (PolygonViewModels == null) return;

        foreach (var vm in PolygonViewModels)
        {
            var geometry = CanvasGeometry.CreatePolygon(session, vm.Points.ToList());
            foreach (var cutout in vm.Cutouts)
            {
                var cutoutGeo = CanvasGeometry.CreatePolygon(session, cutout.ToList());
                geometry = geometry.CombineWith(cutoutGeo, Matrix3x2.Identity, CanvasGeometryCombine.Exclude);
            }
            session.DrawGeometry(geometry, Colors.Black, 2);
        }
    }

    // 拖拽处理示例:修改ViewModel的Points,自动同步回模型
    private void HandleDragOperation(PolygonViewModel targetVm, Vector2 dragDelta)
    {
        for (int i = 0; i < targetVm.Points.Count; i++)
        {
            targetVm.Points[i] = new Vector2(
                targetVm.Points[i].X + dragDelta.X,
                targetVm.Points[i].Y + dragDelta.Y);
        }
    }
}

4. XAML绑定

<local:CustomCanvasControl 
    PolygonViewModels="{Binding PolygonViewModels, Mode=TwoWay}" />

分层合规性说明

  • 模型层(Polygon):仅负责数据存储和核心业务逻辑,不依赖任何视图/ViewModel组件。
  • ViewModel层:PolygonViewModel封装模型,提供视图所需的可绑定属性和操作,同步视图与模型的数据;MainViewModel管理ViewModel集合,通过DI解耦实例化逻辑。
  • 视图层:自定义画布仅与ViewModel交互,不直接引用模型,渲染和操作均通过ViewModel同步回数据层。

内容的提问来源于stack exchange,提问作者TheSpixxyQ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 04:38:11