开发者

Silverlight with MVVM : How to access Event of the ViewModel from the View?

开发者 https://www.devze.com 2023-03-30 21:41 出处:网络
I have a MVVM application and somewhere in the application our company use a Third-Party that cannot use {Binding}. It\'s a component that draw shapes, etc. What I want it, when the ViewModel load fro

I have a MVVM application and somewhere in the application our company use a Third-Party that cannot use {Binding}. It's a component that draw shapes, etc. What I want it, when the ViewModel load from the persisted storage all shapes to notify the View to draw them. In a perfect world I would just have the take the Third-party and bind it to the ViewModel Shapes collection but I cannot.

From there, my idea was that I could get from the View the ViewModel (via the DataContext) and to hook the PropertyChanged event. The problem is that the DataContext is not yet initialized in the constructor, so it's NULL, and I cannot hook the event. Here is a sample of the code:

        public CanvasView()
        {
            InitializeComponent();
            ((CanvasViewModel)this.DataContext).PropertyChanged += new PropertyChangedEventHandler(CanvasView_PropertyChanged); //Exc开发者_Python百科eption Throw here because DataContext is null
        }

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

How can I get information from my ViewModel to my View in that case?


All of the answers so far breaks the MVVM pattern with having code-behind on the view. Personally I would wrap the 3rd party control in a UserControl and then wire up a few dependency properties with property change events.

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>


you could also attach your handler in the Loaded event.

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(); 
   }
}


I want to comment Dennis Roche answer. Really, in this case we can use wrap approach, because we need to redraw view when Shapes collection changed. But view model logic can be too complex, and ,for instance, instead of redraw on PropertyChanged we should redraw on some custom event (f.i. ModelReloadEvent). In this case, wrapping doesn't help, but subscription on this event does, as in Muad'Dib solution - view model use event based communication with view, but this event should be view specific.

Using code-behind with View specific logic doesn't break MVVM. Yes, this code can be decorated with behavior/action, but using code behind - just simple solution.

Also, take a look at this view on MVVM. According to structure, ViewModel knows about abstract IView.If you worked with Caliburn/Caliburn.Micro MVVM frameworks you remember ViewAware class and IViewAware, which allows get view in view model.

So, more flexible solution I guess is next:

View:

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();
    }
}


Use the DataContextChanged event on the View (Window or UserControl)

    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(); 
        }
    }

update: Here is a documented way to get DataContextChanged in Silverlight 3 and 4 http://www.lhotka.net/weblog/AddingDataContextChangedInSilverlight.aspx

0

精彩评论

暂无评论...
验证码 换一张
取 消