【问题标题】:Manipulate View from ViewModel从 ViewModel 操作视图
【发布时间】:2014-08-08 15:07:54
【问题描述】:

早上好,提前感谢您的回答。

我的视图实现了一个平移和缩放库:A WPF Custom Control for Zooming and Panning

在视图中,有一个控件可以根据鼠标双击放大到一个点:

private void zoomAndPanControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
   if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
   {
      Point doubleClickPoint = e.GetPosition(content);
      zoomAndPanControl.AnimatedSnapTo(doubleClickPoint);
   }
}

我想强制“zoomAndPanControl.AnimatedSnapTo(doubleClickPoint);”当我拉动它时,根据我绘制的几何点的 ViewModel 数据到一个特定的点。因此,视图将平移到新几何 x,y 坐标的点。几何体/点已绑定到视图。

作为补充说明,提取几何点的数据是在 DispatchTimer 中进行的。随着新几何坐标的读取,我希望视图可以平移并跟随这些坐标。

当我获取数据时,是否有一种简单的方法可以从 ViewModel 访问此控件?可能用自定义点模拟鼠标事件?我不确定最好的方法。

【问题讨论】:

  • 首先 - 你的 ViewModel 和 View,在真正的 MVVM 中永远不应该相遇并且彼此一无所知。因此,告诉您的视图从视图模型中的数据执行某些操作需要绑定或数据触发器。查看您所显示的内容,对我来说最简单的事情是在您的 ZoomAndPanControl 中创建一个依赖属性,该属性在设置后会导致您所追求的行为(调用 AnimatedSnapTo) - 然后该属性可以绑定到您的视图模型。更改您的视图模型属性,绑定将触发操作。
  • @kidshaw 这不是真的。视图将持有对 viewModel 的引用。这是使数据绑定工作的唯一方法。视图将不可避免地知道视图模型。视图模型永远不应该知道它的用户。
  • 好的,有道理。将依赖属性添加到 ZoomAndPanControl 库,以便我可以绑定。我希望有一个更简单的解决方案,因为视图已经可以使用控件......但我猜它就是这样。
  • @Shoe - 是的,我夸大了那里的分离。我倾向于实现 View Model Locator 模式,所以习惯于认为它们是通过接口分开的——你是对的。
  • 我会将我的建议作为答案发布,以防它对您有用。

标签: c# wpf mvvm viewmodel


【解决方案1】:

要通过绑定控制您的视图,您的控件需要绑定一些东西。

  1. 向您的自定义控件添加一个接受“点”类型的依赖属性。
  2. 使用从您的视图模型实现的 INotifyPropertyChanged 将属性绑定到该 DP - 请注意,如果您不想在 VM 中使用“Point”类,可以在此处添加 ValueConverter。
  3. 在 DP 的定义中,调整 setter 以触发 AnimatedSnapTo(...) 方法

希望对您有所帮助。

【讨论】:

    【解决方案2】:

    我通常处理这类场景的方式是在 ViewModel 上定义一个委托,在 View 中设置它,并在需要时调用它:

    在您的 ViewModel 中定义一个方法,该方法接受一个 Action<Point> 和一个 Point 对象作为当前鼠标位置:

    public void ExecuteAnimatedSnapTo(Action<Point> animatedSnapToAction, Point pointerPosition)
    {
        if (animatedSnapToAction != null && pointerPosition != null)
        {
            // Create a new point based on the one passed in and data in ViewModel
            Point newPoint =
                new Point(pointerPosition.X + viewModelData.X, pointerPosition.Y + viewModelData.Y);
    
            // Invoke the delegate using the new point
            animatedSnapToAction(newPoint);
        }
    }
    

    然后在你View的代码后面,执行这个方法:

    private void zoomAndPanControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
       if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0)
       {
          Point doubleClickPoint = e.GetPosition(content);
    
          var viewModel = (MyViewModel)this.DataContext;
    
          viewModel.ExecuteAnimatedSnapTo(zoomAndPanControl.AnimatedSnapTo, doubleClickPoint);
       }
    }   
    

    使用这种方法,您仍然可以保持 View 与 ViewModel 的隔离。当 [unit] 测试 VM 时,委托和点在传递给方法时可能为空。然后if 块阻止“UI”逻辑被测试。

    您需要注意的一件事是,如果 ViewModel 数据是在不同的线程上计算的,您必须在 UI 调度程序上执行委托。

    编辑

    我的印象是,当MouseDoubleClick 被触发时,ViewModel 拥有调用委托所需的所有数据。如果不是这种情况,更好的解决方案是将Action 公开为VM 上的属性并在需要时调用它:

    public Action<Point> AnimatedSnapToAction { get; set; }
    

    当您在 View 上创建 VM 实例时,也要设置属性:

    public MyView()
    {
        InitializeComponent();
    
        MyViewModel viewModel = new MyViewModel();
        viewModel.AnimatedSnapToAction = zoomAndPanControl.AnimatedSnapTo;
    
        this.DataContext = viewModel;
    }
    

    现在您可以在需要时在 VM 上执行委托。例如,如果需要在 DispatcherTimer 的刻度上调用它,它看起来像这样:

    private void dispatcherTimer_Tick(object sender, EventArgs e)
    {
        // Calculate geometry data
    
        if(AnimatedToSnapAction != null)
        {
            AnimatedSnapToAction(pointCalculatedUsingGeometryData);
        }
    }
    

    【讨论】:

    • 这很有趣,我还在纠结。据我所知,在此示例中,双击正在驱动“AnimatedSnapTo”。当视图模型获取几何点的新数据(绑定在视图中)时,我希望调用“AnimatedSnapTo”。这样我就可以平移到该点的新位置。
    • 这将在 ViewModel 上调用 AnimatedSnapTo。您的视图需要来自 ViewModel 的数据才能调用zoomAndPanControl.AnimatedSnapTo()。另一方面,您不想将整个控件或视图暴露给 ViewModel。这里发生的是ExecuteAnimatedSnapTo 方法接受一个“回调”操作,在这种情况下,它恰好指向 UI 上的“某物”。 VM 不知道也不关心Action 是什么或它来自哪里,它只是调用它;这就是它的全部优点,因为您没有打破 View 和 ViewModel 之间的隔离。
    • 哦,我想我误解了你的问题。我假设当 MouseDoubleClick 被触发时,VM 已经拥有创建新点所需的额外数据。不是这样吗?
    • 不,这是一个有趣的解决方案,但我认为它不适用于我的情况。数据来自 ViewModel 上的 DispatchTimer,它设置几何对象的 x/y,并绑定在视图中。因此,从逻辑上讲,AnimatedSnapTo 将在拉取数据后被调用,因此视图可以平移并遵循这一点。
    • 那么在这种情况下,您可以将Action 公开为属性并在需要时调用它。我会更新我的答案。
    【解决方案3】:

    视图模型应该公开视图需要显示和交互的数据和行为,它不应该引用视图本身。

    View 隐式或显式地引用了 ViewModel。通过此引用,您可以调用方法、绑定数据、连接事件等...

    在这种情况下,为了简单起见,您可能希望在用户双击控件时在 ViewModel 上执行命令,然后可以在命令执行中修改 ViewModel 公开的任何公共属性。然后可以使用这些属性来确定要缩放到的点。

    【讨论】:

    • 这是我的困境,我想以自定义方式使用“AnimatedSnapTo”。双击工作正常,在视图中,因为它是一个视图控件。但是我正在使用视图模型绑定的画布上绘制形状,并且它想将此“AnimatedSnapTo”强制到形状的位置。
    • 除了形状之外,您的 ViewModel 实际上代表什么?
    • 视图模型保存形状的数据,即 x/y 坐标、名称等。它们绑定到视图,显示在画布上。添加时,我正在尝试平移到那些 x/y 坐标。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-02
    • 2018-03-16
    • 2018-02-12
    • 1970-01-01
    • 2013-08-28
    • 2010-11-27
    • 2010-12-01
    相关资源
    最近更新 更多