如何从继承的用户控件访问 viewModel 依赖属性?

Posted

技术标签:

【中文标题】如何从继承的用户控件访问 viewModel 依赖属性?【英文标题】:how to access to viewModel dependency properties from inherited user control? 【发布时间】:2020-08-08 01:59:56 【问题描述】:

我在演示 WPF 项目中遇到了困难。我必须认识到我有许多使代码复杂化的约束。

核心元素是一个继承自 UserControl 的控件。我想尽可能保持其代码隐藏。另外,我希望将其 XAML 放在 ControlTemplate 中。它的 C# 代码应该在一个专用的 ViewModel 中(这个例子是一个巨大的项目,拥有一个专用的 viewModel 可以帮助将所有的 viewmodel 分组。但无论如何,说它是强制性的)。 最后但同样重要的是,我想将此控件的 2 个属性绑定到外部属性。

这是我的 MainWindow.xaml 文件:

<Window x:Class="ViewModel_defined_in_ControlTemplate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="StaticResource TextBoxTemplate"
                                 NomPersonne="sg"/>
            <Button Content="Click me!" Command="Binding ElementName=MyViewModel,Path=ChangeTextBoxContent" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

该按钮只是更改 NomPersonne 依赖属性的值(见下文)。 MyDictionary.xaml 包含 ControlTemplate:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

    <ControlTemplate x:Key="TextBoxTemplate" TargetType="x:Type local:MyUserControl">
        <Grid>
            <Grid.DataContext>
                <local:MyViewModel/>
            </Grid.DataContext>
            <TextBox Width="50" HorizontalAlignment="Left" Text="TemplateBinding NomPersonne"/>
        </Grid>


    </ControlTemplate>    
</ResourceDictionary>

我不知道将我的依赖属性放在哪里,以及如何访问它。 我试着把它放在 MyUserControl 中:

namespace ViewModel_defined_in_ControlTemplate

    public partial class MyUserControl : UserControl
    



        public string NomPersonne
        
            get  return (string)GetValue(NomPersonneProperty); 
            set  SetValue(NomPersonneProperty, value); 
        

        public static readonly DependencyProperty NomPersonneProperty =
            DependencyProperty.Register("NomPersonne", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));

    

现在可以从 MyUserCONtrol 的 XAML 访问它,但是我不知道如何访问它以便让按钮的命令更改属性:

namespace ViewModel_defined_in_ControlTemplate

    public class MyViewModel : ViewModelBase
    

        public RelayCommand ChangeTextBoxContent = new RelayCommand(() => 
         
            //...
        , () => true);

    


我宁愿在视图模型中拥有依赖属性,但在这种情况下,我如何在 MainWindow 中的 MyUserControl 的 XAML 中访问?

谢谢。

【问题讨论】:

【参考方案1】:

您应该向视图模型添加一个 source 属性,将UserControltarget 属性绑定到该属性并更新视图模型中的源属性:

<local:MyUserControl Template="StaticResource TextBoxTemplate" 
                     NomPersonne="Binding Name"/>

查看模型:

public class MyViewModel : ViewModelBase

    public RelayCommand ChangeTextBoxContent = new RelayCommand(() =>
    
        Name = "...":
    , () => true);

    private string _name;

    public string Name
    
        get  return _name; 
        set  _name = value; OnPropertyChanged(); 
    

    ...

您还应该在窗口中设置DataContext 而不是ResourceDictionary

<Window x:Class="ViewModel_defined_in_ControlTemplate.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <local:MyUserControl Template="StaticResource TextBoxTemplate"
                                 NomPersonne="Binding Name"/>
            <Button Content="Click me!" Command="Binding ChangeTextBoxContent" Width="100" HorizontalAlignment="Left"/>
        </StackPanel>
    </Grid>
</Window>

ResourceDictionary 应该只定义模板:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:ViewModel_defined_in_ControlTemplate">

        <ControlTemplate x:Key="TextBoxTemplate" TargetType="x:Type local:MyUserControl">
            <Grid>
                <TextBox Width="50" HorizontalAlignment="Left" 
                         Text="TemplateBinding NomPersonne"/>
            </Grid>
        </ControlTemplate>
    </ResourceDictionary>

【讨论】:

【参考方案2】:

给 UserControl 一个 ViewModel 是一个非常糟糕的主意。您刚刚偶然发现了这种方法的许多问题之一。没有办法在 ViewModel 中定义 DependecyProperty,代码隐藏是唯一的方法。

为了在代码隐藏和 ViewModel 之间同步数据,您必须在代码隐藏中订阅 ViewModel 的 PropertyChanged,并且每次 ViewModel 中的值发生变化时,更新代码隐藏中的相应 DependencyProperties。这也必须反过来。当 DependencyProperty 更改时,您必须在 ViewModel 中更新它。实现这一点并非不可能,但它真的很难看(相信我,我已经做到了;以后再也不会了)。

另一个问题是将 UserControl 的 DataContext(在代码隐藏或 XAML 中)设置为 ViewModel。如果直接在 UserControl 上设置它,绑定将不起作用。解决方法是设置 UserControl 的第一个子项的 DataContext(同样,不要这样做)。

带有 ViewModel 的 UserControl 是一个非常糟糕的主意。但这并不是说您的代码隐藏应该包含所有代码。您始终可以将执行某些高级逻辑的方法提取到它们单独的类中。静态方法可以在任何地方调用,甚至可以在代码隐藏中调用。

【讨论】:

以上是关于如何从继承的用户控件访问 viewModel 依赖属性?的主要内容,如果未能解决你的问题,请参考以下文章

Caliburn.Micro-如何从继承的ViewModel在WPF视图中显示多个项目:Conductor 。Collection.AllActive

如何在ViewModel中访问View中的控件

如何在 viewmodel 中访问 mvvm 模型中的控件?

页面与ViewModel(上)

如何从ViewModel访问附加到窗口的Behaviors属性

ViewModel应该继承WPF中的DependencyObject吗?