从Viewmodel访问View

5
我知道这不是很好的设计,但我需要从我的ViewModel访问视图。这是因为我有一些旧的控件(例如Winforms控件)不支持绑定并且需要通过代码填充。
我正在使用AvalonDock 2.0的MVVM模型,并且有类似于以下内容:
   <ad:DockingManager x:Name="dockManager" 
                  DocumentsSource="{Binding Files}"
                  AnchorablesSource="{Binding Tools}"
        ActiveContent="{Binding ActiveDocument, Mode=TwoWay, Converter={StaticResource ActiveDocumentConverter}}">
        <ad:DockingManager.LayoutItemTemplateSelector>
            <local:PanesTemplateSelector>
                <local:PanesTemplateSelector.NavigationViewTemplate>
                    <DataTemplate>
                        <tvext:TreeViewExtended />
                    </DataTemplate>
                </local:PanesTemplateSelector.NavigationViewTemplate>
            </local:PanesTemplateSelector>
        </ad:DockingManager.LayoutItemTemplateSelector>

所以NavigationViewTemplate模板绑定到集合Tools的一个项目,该集合是我类型为NavigationViewModel的ViewModel。
我可以将TextBox绑定到我的视图模型的属性,但我不知道如何从NavigationViewModel中访问模板内的tvext:TreeViewExtended控件以填充它。
谢谢!Michael

1
不要害怕在代码后面添加一些额外的代码来实现这个目的。如果你想要将你的视图模型与不同的UI共享,那么这样做会使这个过程变得困难。你的视图模型为UI提供服务,但不应直接依赖于特定的UI组件。 - Derreck Dean
3个回答

8

是的,我不太喜欢让ViewModel知道视图,但既然你问了,这里有一个想法:

 1. Create an interface for your View (if you haven't already) and add whatever functionality to that interface that you need access to from the ViewModel. Lets call it ISomeView
 2. add/implement the interface on the View
 3. add property to the ViewModel ISomeView View {get;set;} 
 4. in the view depending where the ViewModel is being injected assign populate the ViewModel's property, for example you can do it on DataContextChanged:

    private void OnDataContextChanged (object sender, ...EventArgs e)
    {
         // making up your ViewModel's name as ISomeViewModel
         ((ISomeViewModel)sender).View = this;
     }

6
在您的视图模型中创建事件,然后在视图中订阅这些事件,因此视图和视图模型仍然不是强耦合的,您可以得到想要的内容。

4
我建议您不要从ViewModel访问Winforms控件。将所有与视图相关的内容保留在视图中。您可以按照以下方式实现这一点:
  1. Create a WPF custom control, e.g. named TreeViewExtendedWrapper. (See this article for a short tutorial how to create custom WPF controls).

  2. Inside the control template of the custom control (in the Themes\Generic.xaml file), place your Winforms control:

    <ControlTemplate TargetType="{x:Type local:TreeViewExtendedWrapper}">
        <Border Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}">
            <tvext:TreeViewExtended />
        </Border>
    </ControlTemplate>
    
  3. Add dependency properties to your custom control for all Winforms control properties that you need to bind to the ViewModel.

  4. Also add dependency properties to your custom control for all commands that you need to bind to the view model.

  5. Write C# code inside the code-behind of the custom control to connect the dependency properties of your custom control to the properties, events, and methods of the Winforms control.

  6. Inside your data template, place your custom control with any necessary data bindings:

    <DataTemplate>
        <local:TreeViewExtendedWrapper MyProperty={Binding MyProperty}/> 
    </DataTemplate> 
    
使用这种方法,您可以使用数据绑定来连接ViewModel和Winforms控件,即不违反MVVM原则。

我正在尝试您的方法。但是由于我从未编写过自定义WPF控件,您能告诉我如何在TreeViewExtendedWrapper中建立DataContextChanged的处理程序吗? - MTR
您可以使用方法DependencyProperty.OverrideMetadata()来实现此操作。这篇文章包含一些示例代码,展示了如何在DataContext属性更改时接收通知。 - user128300
文章中的示例无法运行,我收到错误 CS0123:Keine Überladung für "OnDataContextChanged" stimmt mit dem Delegaten "System.Windows.PropertyChangedCallback" überein。 - MTR
在该文章中,属性OnDataContextChanged的签名似乎是错误的。它应该是static void OnDataContextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - user128300
我接受这个答案是正确的,因为有包装器的想法。虽然自定义控件对于这个目的来说太复杂了,我选择了用户控件。 - MTR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接