【问题标题】:How to set a parameter for a user control that has a view model in WPF如何在 WPF 中为具有视图模型的用户控件设置参数
【发布时间】:2018-01-21 17:39:59
【问题描述】:

在 WPF 中,我有一个包含用户控件的窗口。窗口和用户控件各有一个视图模型。我想将一个参数从窗口的 VM 传递给 UC 的 VM。经过相当多的寻找,我还没有找到方法。

窗口 XAML 将其数据上下文设置为其 VM。 UC 包含参数的自定义依赖属性。我想使用SetBinding将DP绑定到UC VM。

如果我将 UC 数据上下文设置为其 VM,则参数绑定不起作用。如果我不设置 UC 数据上下文,则参数绑定有效,但未引用 UC VM。

如何传递参数并绑定到 UC VM?

UC XAML

<UserControl x:Name="userControl" x:Class="Test_Paramaterized_UserControl_with_MVVM.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Test_Paramaterized_UserControl_with_MVVM"
             xmlns:view="clr-namespace:Daavlin.SmartTouch.STUV_WPF.View"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
      <Grid Margin="10">
        <Border BorderThickness="3" BorderBrush="Black" Padding="10">
            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="UserControl1 View: "/>
                    <TextBlock Text="{Binding ElementName=userControl, Path=PropUserControlView, Mode=OneWay}" FontWeight="Bold"/>
                </StackPanel>
                <Rectangle Height="5"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="UserControl1 ViewModel: " />
                    <TextBlock Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
                        <TextBlock.DataContext>
                            <local:UserControl1ViewModel/>
                        </TextBlock.DataContext>
                    </TextBlock>
                </StackPanel>
            </StackPanel>
        </Border>
    </Grid>
</UserControl>

UC 代码隐藏和虚拟机

public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        public string PropUserControlView { get => (string)GetValue(PropUserControlViewProperty); set => SetValue(PropUserControlViewProperty, value); }
        public static readonly DependencyProperty PropUserControlViewProperty =
            DependencyProperty.Register(nameof(PropUserControlView), typeof(string), typeof(UserControl1),
                new PropertyMetadata(null, DependencyPropertyChanged));

        private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var x = dependencyPropertyChangedEventArgs.NewValue;
        }
    }

    public class UserControl1ViewModel : ObservableObject
    {
        public string PropUserControlViewModel { get => _propUserControlViewModel; set => SetField(ref _propUserControlViewModel, value); }
        private string _propUserControlViewModel = "value from UserControl-ViewModel";
    }

窗口 XAML

<Window x:Class="Test_Paramaterized_UserControl_with_MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Test_Paramaterized_UserControl_with_MVVM"
        Title="MainWindow" >
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid VerticalAlignment="Top" >
        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Margin="20">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="MainWindow1 ViewModel: "/>
                <TextBox Text="{Binding PropWindowViewModel, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold"/>
            </StackPanel>
            <Rectangle Height="10"/>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="UserControl1 (fixed value Fixed): " VerticalAlignment="Center"/>
                <local:UserControl1 PropUserControlView="Fixed"/>
            </StackPanel>
            <Rectangle Height="10"/>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="UserControl1 (bound to MainWindows VM): " VerticalAlignment="Center"/>
                <local:UserControl1 PropUserControlView="{Binding PropWindowViewModel}"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

窗口代码隐藏和虚拟机

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

public class MainWindowViewModel : ObservableObject
{
    public string PropWindowViewModel { get => _propWindowViewModel; set => SetField(ref _propWindowViewModel, value); }
    private string _propWindowViewModel = "valuefrom Window-VIewModel";
}

【问题讨论】:

  • "窗口和用户控件各有一个视图模型。我想将一个参数从窗口的 VM 传递给 UC 的 VM。"这首先是错误的方法。 UserControl 永远不应该有自己的视图模型。相反,它应该公开绑定到“外部”视图模型的依赖属性,该视图模型通过从其父元素的值继承或通过在 XAML 中声明 UserControl 时直接分配 DataContext 属性传递给 UserControl 的 DataContext。永远不要在自己的 XAML 或代码中显式设置 UserControl 的 DataContext。

标签: c# wpf xaml


【解决方案1】:

据我所知,您的意思是:-

1) 你有一个用户控件,它有自己的视图模型。

2) 你有一个窗口,你有自己的视图模型。

您想要链接两者并将参数从 WindowViewModel 传递到 UserControlViewModel。

您可以做的是,在 WindowViewModel 中保留 UserControlViewModel 类型的属性(例如 UCViewModel),并将 XAML 中用户控件的数据上下文设置为

<local:UserControl1 DataContext="{Binding UCViewModel}" .../>

现在您可以通过 WindowViewModel 访问 UserControlViewModel 中的任何内容,您可以设置任何属性值或将任何参数从 WindowViewModel 传递给 UserControlViewModel。

如果您需要代码参考,请告诉我。我们一直在以类似的方式使用用户控件,并且效果很好。

【讨论】:

  • 我快速尝试了一下,但没有成功。能给个代码指针吗?在任何情况下,这都有 Window 和 UC VM 之间强引用的缺点,因此您不能独立于 UC VM 使用 Window。
【解决方案2】:

我想使用 SetBinding 将 DP 绑定到 UC VM。

这真的是一个要求吗? SetBinding() 要求目标属性是依赖属性,这反过来又要求目标对象是依赖对象。您的视图模型对象不是依赖对象,当然它的所有属性都不是依赖属性。

实现该目标需要对代码进行比其他明显必要的更大的更改。

如果我将 UC 数据上下文设置为其 VM,则参数绑定不起作用

为什么不呢?您没有显示尝试此操作的代码,因此很难理解您的意思。无论如何,让用户控件设置自己的DataContext 并不是一个好主意。该属性是公开的,您不希望将实现细节暴露给客户端代码。这样做会导致客户端代码将 DataContext 设置为错误的错误,从而禁用 UserControl 中的所有内容。

也就是说,如果 “参数绑定” 是指 MainWindow XAML 中的绑定,将 {Binding PropWindowViewModel} 分配给用户控件的 PropUserControlView 属性,那么 只是 设置用户控件的DataContext 应该不会影响这一点。您仍然在用户控件中拥有依赖属性,并且在用户控件中绑定的任何内容都应该仍然有效。

最后,为什么要将依赖属性绑定到视图模型并不完全清楚。在用户控件的 XAML 中,您可以(正如您已经完成的那样)直接绑定到用户控件的依赖项属性。不需要视图模型中的属性来复制它。

也许您在视图模型的其他地方有代码想要响应这个值的变化?不清楚,而且在不了解整个故事的情况下很难给出最好的建议。

话虽如此,您在上面发布的代码只需进行一些小改动即可使用。首先,您需要公开创建视图模型的TextBlock,以便用户控件代码隐藏可以访问它:

<TextBlock x:Name="textBlock1" Text="{Binding PropUserControlViewModel, FallbackValue=propUserControlViewModel 2}" FontWeight="Bold">
  <TextBlock.DataContext>
    <l:UserControl1ViewModel/>
  </TextBlock.DataContext>
</TextBlock>

即将x:Name="textBlock1" 添加到声明中。

然后,您需要使用依赖属性的属性更改通知在依赖属性更改时随时更新视图模型属性:

private static void DependencyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    UserControl1 uc = (UserControl1)dependencyObject;
    UserControl1ViewModel vm = (UserControl1ViewModel)uc.textBlock1.DataContext;
    vm.PropUserControlViewModel = (string)dependencyPropertyChangedEventArgs.NewValue;
}

以上在您的有限示例中有效,但您可能希望为 DependencyPropertyChanged() 方法提供一个更具描述性的名称,具体到所讨论的实际属性。

如果您确实选择以这种方式在视图模型中镜像依赖属性,恕我直言,更好的方法是设置用户控件的根元素(即Grid),以便其数据上下文是您的视图模型,然后在整个 XAML 的其余部分中,将 only 绑定到视图模型。混合视图模型和依赖属性本身并没有错,但它确实会引入不一致,从而使测试和维护代码变得更加困难。

【讨论】:

  • 谢谢。重新要求 SetBinding:这对我来说太具体了。我只是希望能够拥有将 Window VM 与 UC VM 链接起来的绑定。当 UC DataContext 设置时,重新参数传递不起作用:请参阅我的问题 stackoverflow.com/q/48099204/468523。我知道有一些变通方法(我已经尝试过您描述的 vm. 方法)但想知道绑定可以在整个过程中使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
相关资源
最近更新 更多