wpf - WPF MVVM 用户控件的最佳实践。避免绑定(bind)问题

标签 wpf data-binding mvvm user-controls wpf-controls

我正在努力成为一名优秀的士兵并设计一些简单的用户控件以用于 WPF MVVM 应用程序。我正在尝试(尽可能)让 UserControls 自己使用 MVVM,但我认为调用应用程序不应该知道这一点。调用应用程序应该能够轻击用户控件,也许设置一两个属性,也许订阅事件。就像他们使用常规控件(ComboBox、TextBox 等)时一样,我需要花很多时间来正确绑定(bind)。注意下面 View 中 ElementName 的使用。这不是使用 DataContext。事不宜迟,这是我的控制:

<UserControl x:Class="ControlsLibrary.RecordingListControl"
         ...
         x:Name="parent"

         d:DesignHeight="300" d:DesignWidth="300">
<Grid >
    <StackPanel Name="LayoutRoot">
    <ListBox ItemsSource="{Binding ElementName=parent,Path=Recordings}" Height="100" Margin="5" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=FullDirectoryName}" />
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    </StackPanel>

</Grid>

在后面的代码中(如果有办法避免后面的代码,请告诉我)...

public partial class RecordingListControl : UserControl
{
    private RecordingListViewModel vm = new RecordingListViewModel();
    public RecordingListControl()
    {
        InitializeComponent();

        // I have tried the next two lines at various times....
       // LayoutRoot.DataContext = vm;
        //DataContext = vm;
    }


    public static FrameworkPropertyMetadata md = new FrameworkPropertyMetadata(new PropertyChangedCallback(OnPatientId));
    // Dependency property for PatientId
    public static readonly DependencyProperty PatientIdProperty =
   DependencyProperty.Register("PatientId", typeof(string), typeof(RecordingListControl), md);

    public string PatientId
    {
        get { return (string)GetValue(PatientIdProperty); }
        set { SetValue(PatientIdProperty, value);
        //vm.SetPatientId(value);
        }
    }

    // this appear to allow us to see if the dependency property is called.
    private static void OnPatientId(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        RecordingListControl ctrl = (RecordingListControl)d;
        string temp = ctrl.PatientId;     
    }

在我的 ViewModel 中,我有:

public class RecordingListViewModel : ViewModelBase
{
    private ObservableCollection<RecordingInfo> _recordings = null;// = new ObservableCollection<string>();
    public RecordingListViewModel()
    {

    }
    public ObservableCollection<RecordingInfo> Recordings
    {
        get
        {
            return _recordings;
        }
    }

    public void SetPatientId(string patientId)
    {
        // bunch of stuff to fill in _recordings....
        OnPropertyChanged("Recordings");
    }

}

然后我把这个控件放在我的主窗口中,就像这样:
<Grid>
    <ctrlLib:RecordingListControl  PatientId="{Binding PatientIdMain}" SessionId="{Binding SessionIdMain}" />
    <Label Content="{Binding PatientIdMain}" /> // just to show binding is working for non-controls
</Grid>

运行所有这些时出现的错误是:
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''RecordingListControl' (Name='parent')'. BindingExpression:Path=Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

显然我有某种绑定(bind)问题。这实际上比我得到的要远得多。至少我在后面的控件代码中打了代码:
OnPatientId。

之前,我在用户控件中没有 ElementName 并且正在使用 DataContext 并且收到绑定(bind)错误,表明 PatientIdMain 被视为用户控件的成员。

有人可以指出在 MVVM 应用程序中使用带有 MVVM 设计的用户控件的示例吗?我认为这是一个相当普遍的模式。

让我知道我是否可以提供更多详细信息。

非常感谢,
戴夫

编辑 1
我尝试了 har07 的想法(见答案之一)。我有:
如果我尝试:

ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"

我明白了
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=DataContext.Recordings; DataItem='RecordingListControl' (Name='parent'); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

如果我尝试:

ItemsSource="{Binding Recordings}"

我明白了
System.Windows.Data Error: 40 : BindingExpression path error: 'Recordings' property not found on 'object' ''MainViewModel' (HashCode=59109011)'. BindingExpression:Path=Recordings; DataItem='MainViewModel' (HashCode=59109011); target element is 'ListBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

我认为他的第一个想法(也许是他的第二个)非常接近,但回想一下,Recordings 是在 ViewModel 中定义的,而不是在 View 中定义的。不知何故,我需要告诉 XAML 使用 viewModel 作为源。这就是设置 DataContext 所做的,但正如我在主要部分中所说,这会在其他地方产生问题(您会遇到与从 MainWindown 绑定(bind)到控件上的属性相关的绑定(bind)错误)。

编辑 2. 如果我尝试 har07 的第一个建议:

ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"

并在控件后面添加代码:

RecordingListViewModel vm = new RecordingListViewModel();
DataContext = vm;

我得到:
System.Windows.Data Error: 40 : BindingExpression path error: 'PatientIdMain' property not found on 'object' ''RecordingListViewModel' (HashCode=33515363)'. BindingExpression:Path=PatientIdMain; DataItem='RecordingListViewModel' (HashCode=33515363); target element is 'RecordingListControl' (Name='parent'); target property is 'PatientId' (type 'String')

换句话说,控件看起来不错,但是依赖属性与主窗口的绑定(bind)似乎搞砸了。编译器假定 PatientIdMain 是 RecordingListViewModel 的一部分。
各种帖子表明我无法设置 DataContext 出于这个原因。它会弄乱与主窗口的绑定(bind)。参见例如:
Binding to a dependency property of a user control WPF/XAML并查看 Marc 的答案。

最佳答案

您不应该在 UserControl 中设置 x:Name,因为该控件只有一个 Name 属性,并且通常可以通过 的代码设置。使用 控制。因此,您不能使用 ElementName 绑定(bind)来绑定(bind)到 UserControl 本身的属性。在其内容中绑定(bind)到 UserControl 属性的另一种方法是使用

{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:RecordingsControl}}, Path=Recordings}

同样,使用控件的客户端代码显式或通过继承设置其 DataContext。因此,您在构造函数中创建的 vm 的实例在控件一显示就被丢弃并由继承的 DataContext 替换。

你可以做两件事来解决这个问题:要么在 UserControl 之外创建 vm,例如作为主窗口 ViewModel 的属性,并像这样使用 UserControl:
<my:RecordingsControl DataContext="{Binding RecordingListVM}"/>

这样,您就不需要任何代码,上面的 Binding 只需更改为
{Binding Recordings}

或者,在 UserControl 的代码隐藏文件中创建一个 Recordings 属性,并像我在第一个代码示例中显示的那样绑定(bind)到它。

关于wpf - WPF MVVM 用户控件的最佳实践。避免绑定(bind)问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21771937/

相关文章:

c# - WPF绑定(bind)问题

c# - 通过 CommandParameters 返回 ViewModels 的实例

c# - 如何正确使用 ToggleButton.IsChecked 或 IsPressed 上的触发器?

c# - 在数学回合中使用 'double' 中的逗号

c# - 读取XML后,更改颜色不会影响属性更改

wpf - 如何强制 ItemContainerGenerator 为项目生成容器或如何在启用 UI 虚拟化时将 TreeView 滚动到展开节点?

c# - MVVM模型与WCF有何关系

c# - 如何在 TextBlock 和 ProgressBar 中显示进度?

c# - 窗口关闭后无法设置可见性或调用 Show、ShowDialog 或 EnsureHandle

data-binding - 将 Combobox SelectionChanged 绑定(bind)到命令 MVVM