带MVVM的Silverlight:如何从View中访问ViewModel的事件?

时间:2011-08-26 20:07:05

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

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

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

        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?

4 个答案:

答案 0 :(得分:4)

到目前为止,所有的答案都打破了MVVM模式,在视图上有代码隐藏。我个人会将第三方控件包装在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>

答案 1 :(得分: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(); 
   }
}

答案 2 :(得分:0)

在View(Window或UserControl)上使用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中获取DataContextChanged的文档化方法http://www.lhotka.net/weblog/AddingDataContextChangedInSilverlight.aspx

答案 3 :(得分:0)

我想评论Dennis Roche的回答。 实际上,在这种情况下我们可以使用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();
    }
}