【问题标题】:Silverlight with MVVM : How to access Event of the ViewModel from the View?Silverlight with MVVM:如何从视图访问 ViewModel 的事件?
【发布时间】:2011-11-04 19:37:29
【问题描述】:

我有一个 MVVM 应用程序,我们公司在应用程序的某个地方使用了无法使用 {Binding} 的第三方。它是一个绘制形状等的组件。我想要什么,当 ViewModel 从持久存储中加载所有形状时,通知 View 来绘制它们。在一个完美的世界中,我只需要获取第三方并将其绑定到 ViewModel Shapes 集合,但我不能。

从那里开始,我的想法是我可以从 View 中获取 ViewModel(通过 DataContext)并挂钩 PropertyChanged 事件。问题是DataContext还没有在构造函数中初始化,所以它是NULL,我不能hook事件。以下是代码示例:

        public CanvasView()
        {
            InitializeComponent();
            ((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged); //Exception Throw here because DataContext is null
        }

        void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Shapes")
            {
                DrawShapes(); 
            }
        }

在这种情况下,我如何从我的 ViewModel 获取信息到我的 View?

【问题讨论】:

    标签: c# .net silverlight silverlight-4.0 mvvm


    【解决方案1】:

    到目前为止,所有答案都打破了 MVVM 模式,并在视图中添加了代码隐藏。就我个人而言,我会将第 3 方控件包装在 UserControl 中,然后将一些依赖属性与属性更改事件联系起来。

    C#

    public partial class MyWrappedControl : UserControl
    {
      public static readonly DependencyProperty ShapesProperty = DependencyProperty.Register("Shapes", typeof(ObservableCollection<IShape>), typeof(MyWrappedControl),
          new PropertyMetadata(null, MyWrappedControl.OnShapesPropertyChanged);
    
      public ObservableCollection<IShape> Shapes
      {
        get { return (ObservableCollection<IShape>)GetValue(ShapesProperty); }
        set { SetValue(ShapesProperty, value); }
      }
    
      private static void OnShapesPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
      {
        ((MyWrappedControl)o).OnShapesPropertyChanged(e);
      }
    
      private void OnShapesPropertyChanged(DependencyPropertyChangedEventArgs e)
      {
        // Do stuff, e.g. shapeDrawer.DrawShapes(); 
      }
    }
    

    XAML

    <UserControl 
        Name="MyWrappedControl"
        x:Class="MyWrappedControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
        <!-- your control -->
        <shapeDrawerControl x:Name="shapeDrawer" />
    </UserControl>
    

    【讨论】:

    • 所以不是在 ViewController 中拥有 Shapes 集合,而是在 UserControl 中拥有集合,对吧?
    • 没有。对不起,我的答案是昨晚很仓促写的。 ...您仍然需要在 viewmodel 中有一个 shapes 集合来绑定到用户控件上的依赖项属性。 usercontrol 是一个包装器,用于使非绑定友好控件在 MVVM 中可用。
    • 我已经在单独的答案中评论了这个解决方案。
    • 但是你的用户控件有代码隐藏,所以你仍然像以前一样使用 MVVM。它不是“破坏 MVVM”,因为这是视图逻辑。视图逻辑属于视图。
    • 它就像一个魅力。我也学到了一些新东西。谢谢 Dennis、jrwen 和 Vladimir。
    【解决方案2】:

    您也可以在 Loaded 事件中附加您的处理程序。

    public CanvasView()
    {
       InitializeComponent();
       this.Loaded += this.ViewLoaded;
    }
    
    void ViewLoaded(object sender, PropertyChangedEventArgs e)
    {
       ((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged);    
    }
    
    void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
       if (e.PropertyName == "Shapes")
       {
          DrawShapes(); 
       }
    }
    

    【讨论】:

    • 是的,但它遵循 OP 的代码。它有效。有时您必须对您的模式进行例外处理才能使事情正常进行,尤其是在处理 3rd 方控件/库时。
    【解决方案3】:

    我想评论丹尼斯罗氏的回答。 真的,在这种情况下我们可以使用 wrap 方法,因为当 Shapes 集合发生变化时我们需要重绘视图。但是视图模型逻辑可能太复杂了,例如,我们应该在一些自定义事件(f.i. ModelReloadEvent)上重绘,而不是在 PropertyChanged 上重绘。在这种情况下,包装没有帮助,但订阅此事件确实有帮助,就像在 Muad'Dib 解决方案中一样 - 视图模型使用基于事件的视图与视图通信,但此事件应该是特定于视图的。

    使用 View 特定逻辑的代码隐藏不会破坏 MVVM。是的,这段代码可以用行为/动作来修饰,但是使用后面的代码 - 只是简单的解决方案。

    另外,看看这个view on MVVM。根据结构,ViewModel 知道抽象 IView。如果您使用过 Caliburn/Caliburn.Micro MVVM 框架,您会记得 ViewAware 类和 IViewAware,它允许在视图模型中获取视图。

    所以,我猜接下来是更灵活的解决方案:

    查看:

    public class CanvasView() : ICanvasView
    {
            public CanvasView()
            {
                InitializeComponent();
            }
    
            public void DrawShapes()
            {
              // implementation
            }
    }
    

    ICanvasView:

    public interface ICanvasView
    {
        void DrawShapes();
    }
    

    CanvasViewModel:

    public class CanvasViewModel : ViewAware
    {    
        private ObservableCollection<IShape> _shapes;
        public ObservableCollection<IShape> Shapes
        {
            get
            {
               return _shapes;
            }
            set
            {
               _shapes = value;
               NotifyOfPropertyChange(() => Shapes);
               RedrawView();
            }
        }
    
        private void RedrawView()
        {
            ICanvasView abstractView = (ICanvasView)GetView();
            abstractView.DrawShapes();
        }
    }
    

    【讨论】:

    • 我没有使用 Caliburn/Caliburn.Micro MVVM 框架,我需要看看我是否真的可以在没有它的情况下使用 GetView()(我怀疑)。谢谢你的信息,我会去看看我是否可以在星期一的工作中处理你的信息。
    【解决方案4】:

    在视图(窗口或用户控件)上使用 DataContextChanged 事件

        public CanvasView()
        {
            InitializeComponent();
            Action wireDataContext += new Action ( () => {
                if (DataContext!=null) 
                          ((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged);
                });
            this.DataContextChanged += (_,__) => wireDataContext();
            wireDataContext();
        }
    
        void CanvasView_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Shapes")
            {
                DrawShapes(); 
            }
        }
    

    更新:这是在 Silverlight 3 和 4 http://www.lhotka.net/weblog/AddingDataContextChangedInSilverlight.aspx987654321@中获取 DataContextChanged 的​​记录方法

    【讨论】:

    • 对不起。我不知道你不在 SL5 上。
    猜你喜欢
    • 1970-01-01
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2010-12-07
    • 1970-01-01
    相关资源
    最近更新 更多