在我的MVVM Light应用程序中,我在客户列表中进行搜索。搜索会缩小客户列表,这些列表显示在主/详细视图中,其中包含datagrid(主CustomerSearchResultView)和带有FirstName,Lastname,Address等的单独定义的usercontrol(详细信息 - CustomerSearchDetailView)。以下是主/详细视图的主要内容:
<StackPanel MinWidth="150" >
<TextBlock Text="Customer Search Result List" />
<Grid>
<DataGrid Name="CustomerList" ItemsSource="{Binding SearchResult}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
.....
</DataGrid>
</Grid>
<Grid Grid.Column="2">
<TextBlock Text="Customer Details" Style="{StaticResource Heading2}" Margin="30,-23,0,0"/>
<content:CustomerSearchDetail DataContext="{Binding SelectedRow}" />
</Grid>
</Grid>
</StackPanel>
两者都有相应的ViewModel。请注意CustomerSearchDetail的DC,SelectedRow - 它是CustomerSearchResultViewModel上的一个属性,定义如下:
private Customer _selectedRow;
...
public Customer SelectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
RaisePropertyChanged("SelectedRow");
}
}
...
因为这个我没有在CustomerSearchDetailView上定义任何DC - 它是在&#34; Master&#34;上的Binding中设置的。查看(如上所示),似乎工作正常。
在我的Model文件夹中,我创建了正在使用的Customer类。它实现了ObservableObject和IDataErrorInfo,并具有raisepropertychanged事件的公共属性。
我运行应用程序,一切似乎都没问题。注意:CustomerSearchDetailView的ViewModel(即CustomerSearchDetailViewModel.cs)在这个阶段只是一个空壳而没有使用(据我所知......从不访问构造函数)
现在,我想在详细信息视图中向我的客户添加“保存/更新”功能。好的,我向CustomerSearchDetailView添加了一个Save按钮,如下所示:
<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
我创建了我的&#34; SaveCommand&#34;我的CustomerSearchDetailViewModel中的RelayCommand属性 - 但永远不会被访问。
嗯......好吧,经过一些谷歌来回搜索,我想出了这个: <Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
我定义了&#34; MyCustDetails&#34;作为此视图中指向CustomerSearchDetailViewModel的资源。瞧!我现在在调试时遇到了这个方法...但是,我的客户当然是#34; null&#34;。 (事实上,我花了2个小时在这里实现CommandParameter并将其绑定到主视图上的&#34; SelectedRow&#34;属性 - 但客户仍然是&#34; null&#34;)。
更多谷歌搜索和搜索mvvm示例,我实现了我的&#34; SaveCommand&#34;在Customer类(模型对象)上。你猜怎么着?编辑后的客户传递了 - 我可以将它发送到我的EF层,一切似乎都没问题。
并且 - 如果你还在我身边 - 我的问题就出现了:
1。)我想 - 并且认为这是适当的MVVM方式&#34;做事 - 让我的CRUD / Repository在ViewModel中访问。我怎么能在我的场景中做到这一点?
2。)现在我已经通过Model类(Customer)安装了我的CRUD - 我应该打扰问题1吗?实际上我删除了CustomerSearchDetailViewModel,一切运行正常。我觉得我发明了View - Model(MV)框架...... :-P
我非常希望得到关于此的反馈 - 我为这个&#34;文本墙&#34;道歉。
答案 0 :(得分:2)
假设DC意味着DataContext
我的意见:
SelectedRow
中对CustomerSearchResultViewModel
做了哪些特别的事情? 如果答案是否定的,只需删除该属性,并使用CustomSearchDetailView
将DataGrid
绑定到{Binding ElementName=CustomerList, Path=SelectedItem}
Button
中的CustomerSearchDetailView
需要使用您的保存/更新命令。所以我立即倾向于为该视图使用单独的VM并在那里定义这些命令。现在您提到这些命令未被访问。那么答案就是因为在你的程序中你永远不会真正创建CustomerSearchDetailViewModel
。
正常操作是您的查看DataContext
是它的VM(如果它需要一个。在您的情况下,您需要它来保存您的命令)
查看你的代码我猜你使用MVVM Light。因此,在ViewModelLocator
中,您拥有Main
属性,并且在主视图中,您使用DataContext
属性设置了Main
,并使用Source={StaticResource Locator}
设置了定位符ViewModelLocator
{1}}在App.xaml Resources中创建。这样就为该视图创建了定义该DataContext的ViewModel。你可以在代码隐藏中做同样的事情但是不要偏离主题。
因此,在您的情况下,您将DataContext设置为SelectedRow
,类型为Customer
,并且使用DataContext
解析Binding,这就是为什么当您的命令在Customer
中定义它可以正常工作,但是当它在VM中时它没有。
那么为什么在VM中使用命令并使用
时它才起作用<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
^^之所以有效,是因为DataContext
未被使用,因为Source
已明确指定。在资源中定义了MyCustDetails
,在那里创建了虚拟机。
所以它有什么用呢?
嗯,这太乱了。就像你提到的Customer
那个VM中的细节是空的一样。好吧,我希望你能猜到为什么到现在为止。这是因为您的虚拟机是通过x:Key="MyCustDetails"
在资源中创建的,但它中没有任何内容被使用或与Binding明确引用它时的区别
在这个系统中,我们得到的命令既可以引用明显错误的模型,也可以引用作为资源创建的VM。 DataContext
与&#34; SearchResults&#34;密切相关。查看使未来的扩展或布局更新变得不那么容易。
如果我们保留View&lt; - &gt; VM a 1 - &lt; 1&gt;。 1关系我们可以避免所有这些混乱。总而言之,我们可以一起回答您的问题。虽然这有效,但请不要让您的代码像这样,并调整它以更好地帮助扩展以及遵守一些基本准则。
那我们该怎么做?
方法1:
在CustomerSearchDetail
视图中,添加Customer
类型SelectedCustomer
,我们称之为DataContext="{Binding SelectedRow}"
。
现在将SelectedCustomer="{Binding SelectedRow}"
替换为CustomerSearchResultView
CustomerSerachDetailView
现在将CustomerSerachResultsView
的DataContext设置为与ViewModelLocator
如何链接到其VM的方式类似(通过{x 1}使用{来猜测DataContext绑定{1}})
现在,您可以在Button
CustomerSerachDetailView
<Button Command="{Binding SaveCommand}" ...
中使用SelectedRow
最后因为DataContext
不再是CustomerSerachDetailsView
的{{1}},所以您对FirstName,Lastname,Address的绑定似乎都会停止工作。
我们有很多选择来解决这个问题。
首先在每个Binding中使用一个指向CustomerSerachDetailsView
的RelativeSource FindAncestor绑定,然后通过我们创建的CurrentCustomer DP(DependencyProperty
)获取相应的字段。
例如:
<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />
现在,如果你有多个属性,这很快就会开始变得烦人的打字。然后选择一个共同的祖先(比如这些TextBlock
中的3个被分组在StackPanel
下)并通过与^^类似的绑定将其作为CurrentCustomer
元素应用它。现在StackPanel
的子DataContext将成为Customer元素,因此在他们的每个绑定中你不必完成整个RelativeSource的事情,只需提及{{1} } 等等。
那就是它。现在你有了两个拥有各自VM和模型({Binding Path=FirstName}
)的视图,每个视图都有各自的任务。
太好了,我们完成了吗?错误还没有。
虽然方法1 比我们开始时更好,但仍然只是&#34; meh&#34;。我们可以做得更好。
方法2
MVVMLight有一个Customer
类,允许您以弱依赖格式在不同类之间进行通信。如果你还没有,你需要调查一下。
那么我们如何处理Messenger
?
非常简单:
在Messenger
的{{1}}设置器中,我们会发送一条消息,其中包含新来的SelectedRow
到CustomerSearchResultsViewModel
。
现在value
我们会添加一个属性CustomerSearchDetailsViewModel
并为其分配此传入值。
在CustomerSearchResultsViewModel
我们不再创建DP。这意味着我们不再将CurrentCustomer
设置为来自CustomerSerachDetailsView
的{{1}}中的任何内容(DataContext或DP)(甜蜜少工作:))
至于我们分配SelectedRow
CustomerSerachDetailsView
的方式或我们绑定CustomerSearchResultsView
的方式 - 它们与方法1
最后实际的&#34; FirstName&#34;所以Binding's。那么现在DataContext
是CustomerSerachDetailsView
的属性。所以绑定到它就像Button绑定它的命令
^^这个工作正常现在Button.Command
的{{1}}为CurrentCustomer
是虚拟机,其中存在属性CustomerSearchDetailsViewModel
。