如何从继承的用户控件访问 viewModel 依赖属性?
how to access to viewModel dependency properties from inherited user control?
我在演示 WPF 项目中遇到困难。我必须认识到我有很多使代码复杂化的约束。
核心元素是继承自UserControl的控件。我想尽可能保持它的代码隐藏。另外,我想在 ControlTemplate 中包含它的 XAML。它的 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 包含控件模板:
<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 中访问 in?
谢谢。
你应该添加一个 source 属性 到视图模型,绑定 target 属性 UserControl
到这个并更新视图模型中的源 属性:
<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(); }
}
...
}
您还应该在 window 而不是 ResourceDictionary
中设置 DataContext
:
<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>
给 UserControl 一个 ViewModel 真是个坏主意。您刚刚偶然发现了这种方法的众多问题之一。无法在 ViewModel 中定义 DependecyProperty,代码隐藏是唯一的方法。
为了在代码隐藏和 ViewModel 之间同步数据,您必须在代码隐藏中订阅 ViewModel 的 PropertyChanged,并且每次 ViewModel 中的值发生变化时,都会在代码隐藏中更新相应的 DependencyProperties。这也必须反过来。当 DependencyProperty 更改时,您必须在 ViewModel 中更新它。实现这一目标并非不可能,但它真的很难看(相信我,我已经做到了;再也不会了)。
另一个问题是将 UserControl 的 DataContext(在代码隐藏或 XAML 中)设置为 ViewModel。如果直接在 UserControl 上设置它,绑定将不起作用。解决方法是设置 UserControl 的第一个子项的 DataContext(同样,不要这样做)。
带有 ViewModel 的 UserControl 是一个非常糟糕的主意。但这并不是说您的代码隐藏应该包含所有代码。您始终可以将执行一些高级逻辑的方法提取到它们单独的 类 中。静态方法可以在任何地方调用,甚至是代码隐藏。
我在演示 WPF 项目中遇到困难。我必须认识到我有很多使代码复杂化的约束。
核心元素是继承自UserControl的控件。我想尽可能保持它的代码隐藏。另外,我想在 ControlTemplate 中包含它的 XAML。它的 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 包含控件模板:
<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 中访问 in?
谢谢。
你应该添加一个 source 属性 到视图模型,绑定 target 属性 UserControl
到这个并更新视图模型中的源 属性:
<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(); }
}
...
}
您还应该在 window 而不是 ResourceDictionary
中设置 DataContext
:
<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>
给 UserControl 一个 ViewModel 真是个坏主意。您刚刚偶然发现了这种方法的众多问题之一。无法在 ViewModel 中定义 DependecyProperty,代码隐藏是唯一的方法。
为了在代码隐藏和 ViewModel 之间同步数据,您必须在代码隐藏中订阅 ViewModel 的 PropertyChanged,并且每次 ViewModel 中的值发生变化时,都会在代码隐藏中更新相应的 DependencyProperties。这也必须反过来。当 DependencyProperty 更改时,您必须在 ViewModel 中更新它。实现这一目标并非不可能,但它真的很难看(相信我,我已经做到了;再也不会了)。
另一个问题是将 UserControl 的 DataContext(在代码隐藏或 XAML 中)设置为 ViewModel。如果直接在 UserControl 上设置它,绑定将不起作用。解决方法是设置 UserControl 的第一个子项的 DataContext(同样,不要这样做)。
带有 ViewModel 的 UserControl 是一个非常糟糕的主意。但这并不是说您的代码隐藏应该包含所有代码。您始终可以将执行一些高级逻辑的方法提取到它们单独的 类 中。静态方法可以在任何地方调用,甚至是代码隐藏。