我正在努力成为一名优秀的士兵并设计一些简单的用户控件以用于 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/