Silverlight with MVVM:如何从视图访问ViewModel的事件?

发布于 2024-12-01 18:50:14 字数 838 浏览 0 评论 0原文

我有一个 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?

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); //Exception 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

权谋诡计 2024-12-08 18:50:14

到目前为止,所有答案都打破了 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>

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>
只为一人 2024-12-08 18:50:14

您还可以在 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(); 
   }
}

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(); 
   }
}
飘逸的'云 2024-12-08 18:50:14

我想评论一下丹尼斯·罗奇的回答。
实际上,在这种情况下我们可以使用换行方法,因为当 Shapes 集合更改时我们需要重绘视图。但是视图模型逻辑可能过于复杂,例如,我们应该在某些自定义事件(fi ModelReloadEvent)上重绘,而不是在 PropertyChanged 上重绘。在这种情况下,包装没有帮助,但对此事件的订阅有帮助,如 Muad'Dib 解决方案中一样 - 视图模型使用基于事件的视图通信,但此事件应该是特定于视图的。

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

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

因此,我想接下来是更灵活的解决方案:

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

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();
    }
}
oО清风挽发oО 2024-12-08 18:50:14

在视图(窗口或用户控件)

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

更新上使用 DataContextChanged 事件:以下是在 Silverlight 3 和 4 中获取 DataContextChanged 的​​记录方法 http://www.lhotka.net/weblog/AddingDataContextChangedInSilverlight.aspx

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文