我有一个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?
答案 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();
}
}