我正在努力成为一名优秀的士兵并设计一些简单的用户控件,以便在WPF MVVM应用程序中使用。我正在尝试(尽可能)使UserControls自己使用MVVM,但我不认为调用应用程序应该知道。调用应用程序应该只能打下用户控件,可能设置一个或两个属性,也许订阅事件。就像他们使用常规控件(ComboBox,TextBox等)时一样,我有一点时间让绑定正确。注意在下面的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')
显然我有某种绑定问题。这实际上比我得到的要远得多。至少我正在使用后面的控件代码中的代码: OnPatientId。
之前,我没有在User Control中使用ElementName并且正在使用DataContext并且收到一个绑定错误,表明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')
我认为他的第一个想法(也许是他的第二个想法)非常接近,但回想一下,录像是在ViewModel中定义的,而不是视图。不知何故,我需要告诉XAML使用viewModel作为源。这就是设置DataContext所做的事情,但正如我在主要部分中所说,这会在其他地方产生问题(您会遇到与从MainWindown绑定到控件上的属性相关的绑定错误)。
编辑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')
换句话说,控件看起来很好,但依赖项属性与主窗口的绑定似乎搞得一团糟。编译器假定PatientIdMain是RecordingListViewModel的一部分
各种帖子表明,由于这个原因,我无法设置DataContext。它会破坏绑定到主窗口。参见例如:
Binding to a dependency property of a user control WPF/XAML并查看Marc的回答。
答案 0 :(得分:3)
您不应在UserControl中设置x:Name,因为该控件只有一个Name属性,通常会通过使用控件的代码设置。因此,您无法使用ElementName绑定来绑定UserControl本身的属性。在其内容中绑定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属性,并按照我在第一个代码示例中所示绑定到它。
答案 1 :(得分:1)
如果绑定语句以这种方式改变,你会得到什么:
ItemsSource="{Binding ElementName=parent,Path=DataContext.Recordings}"
或者这样:
ItemsSource="{Binding Recordings}"
如果以上绑定方式之一解决当前绑定错误(“ BindingExpression路径错误:'找不到'录制'属性... ”),但导致另一个绑定错误请发布后一个错误消息。
我认为这部分的正确绑定声明如上所述。
更新:
回复您的修改。尝试在StackPanel级别本地设置DataContext,这样就可以将UserControl设置为不同的DataContext:
public RecordingListControl()
{
InitializeComponent();
RecordingListViewModel vm = new RecordingListViewModel();
LayoutRoot.DataContext = vm;
}
我再次看到你已经尝试了这个,但我认为这是解决特定绑定错误的正确方法(“ BindingExpression路径错误:'PatientIdMain'属性未找到...... ”),所以,让我知道这是否解决了错误,但导致另一个绑定错误。
答案 2 :(得分:1)
一个可能的答案是(灵感来自Dependency property binding usercontrol with a viewmodel)
只需将DataContext用于UserControl,不要使用任何ElementName业务 即。
public partial class RecordingListControl : UserControl
{
public RecordingListViewModel vm = new RecordingListViewModel();
public RecordingListControl()
{
InitializeComponent();
**DataContext = vm;**
}
....
然后,在MainWindow中,您需要像这样绑定用户控件:
<ctrlLib:RecordingListControl
Name="_ctrlRecordings"
PatientId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.PatientIdMain, Mode=TwoWay}"
SessionId="{Binding RelativeSource={RelativeSource
AncestorType=Window},Path=DataContext.SessionIdMain, Mode=TwoWay}" />
我不会将此标记为答案,因为(除了回答自己的问题的大胆)我不太喜欢这个答案。它迫使应用程序员记住把所有关于AncestorType和RelativeSource的废话都放进去。我不认为必须用标准控件来做这个,为什么在这里呢?