1

在我的 MVVM Light 应用程序中,我在客户列表中进行搜索。搜索缩小了显示在主/详细视图中的客户列表,其中包含一个数据网格(主 CustomerSearchResultView)和一个单独定义的带有名字、姓氏、地址等的用户控件(详细信息 - 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 - 它是在“主”视图的绑定中设置的(如上所示),它似乎工作正常。

在我的模型文件夹中,我创建了此处使用的客户类。它实现 ObservableObject 和 IDataErrorInfo 并具有引发propertychanged 事件的公共属性。

我运行应用程序,一切似乎都很好。注意:CustomerSearchDetailView 的 ViewModel(即 CustomerSearchDetailViewModel.cs)在这个阶段只是一个空壳并且没有被使用(据我所见......构造函数永远不会被访问)

现在我想在详细视图中为我的客户添加保存/更新功能。好的,我向 CustomerSearchDetailView 添加了一个保存按钮,如下所示:

<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>

我在 CustomerSearchDetailViewModel 中创建了我的“SaveCommand”RelayCommand 属性 - 但它从未被访问过。

嗯……经过一番谷歌搜索后,我想出了这个:

 <Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>

我在此视图中将“MyCustDetails”定义为指向 CustomerSearchDetailViewModel 的资源。瞧!我现在在调试时点击了该方法……但是,我的客户当然是“空的”。(事实上​​,我在这里花了 2 个小时实现 CommandParameter 并将其绑定到主视图上的“SelectedRow”属性 - 但客户仍然是“null”)。

更多地搜索和搜索 mvvm 示例,我在 Customer 类(模型对象)上实现了我的“SaveCommand”。你猜怎么着?编辑后的客户通过了 - 我可以将其发送到我的 EF 层,一切似乎都很好......

而且 - 如果你还在我身边 - 我的问题来了:

1.) 我想——并且认为这是做事的“正确的 MVVM 方式”——让我的 CRUD/Repository 在 ViewModel 中访问。我怎么能在我的场景中做到这一点?

2.)现在我通过模型类(客户)设置了我的 CRUD - 我应该为问题 1 烦恼吗?事实上,我已经删除了 CustomerSearchDetailViewModel 并且一切运行正常。我觉得我发明了 View - Model (MV) 框架...... :-P

我非常希望得到对此的反馈——我为这个“文字墙”道歉。

4

1 回答 1

2

假设 DC 意味着DataContext

只是我的观点:

  • 第一个问题是你对 in 做了什么特别的事情SelectedRowCustomerSearchResultViewModel

如果答案是否定的,只需摆脱该属性并将您的CustomSearchDetailView绑定直接绑定到DataGridusing{Binding ElementName=CustomerList, Path=SelectedItem}

  • 现在你的保存/更新命令需要被Button's in使用CustomerSearchDetailView。因此,我立即倾向于为该视图使用单独的 VM,并在那里定义这些命令。

现在你提到这些命令没有被访问。那么答案是因为在你的程序中你从来没有真正创建过CustomerSearchDetailViewModel.

正常操作是你的视图DataContext是它的虚拟机(如果它需要一个。在你的情况下你做 imo cos 你需要它来保存你的命令)

查看您的代码,我猜您使用的是 MVVM Light。因此,在ViewModelLocator您拥有您的Main属性并在您的主视图中,您获得了DataContext使用该Main属性的集合,并且Source={StaticResource Locator}Locator 是ViewModelLocator在 App.xaml 资源中创建的。因此,这将为定义该 DataContext 的该视图创建该 ViewModel。你当然可以在代码隐藏中做同样的事情,但我们不要跑题。

因此,在您的情况下,您将 DataContext 设置为SelectedRow类型Customer,并且使用解析绑定DataContext,这就是为什么当您的命令在其中定义Customer时工作正常,但在 VM 中时却没有。

那么为什么当你在你的虚拟机中有命令并使用它时它会起作用

<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>

^^ 之所以有效,是因为已明确指定,DataContext因此未使用。Source并且MyCustDetails在资源中定义的任何地方,都创建了 VM。

所以它起作用了有什么问题?

嗯,这是一个很大的混乱。就像你提到Customer的那个虚拟机的细节是空的。好吧,我希望你现在能猜到为什么会这样。这是因为您的虚拟机是在资源中创建的,x:Key="MyCustDetails"但除了绑定明确引用它时,它没有被使用或设置过

在这个系统中,我们得到的命令要么是指完全错误的模型,要么是指为此目的而作为资源创建的虚拟机。它DataContext与“SearchResults”视图密切相关,这使得未来的扩展或布局更新变得不那么容易。

如果我们将 View <-> VM 保持为 1 <-> 1 的关系,我们可以避免所有这些混淆。因此,总而言之,我们可以一起回答您的两个问题。虽然这可行,但请不要让您的代码像这样并对其进行调整以更好地帮助未来扩展并遵守一些基本准则。

那么我们该怎么做呢?

方法一:

  1. 在您的CustomerSearchDetail视图中,添加一个DependencyProperty类型Customer让我们称之为 say SelectedCustomer

  2. 现在替换DataContext="{Binding SelectedRow}"SelectedCustomer="{Binding SelectedRow}"CustomerSearchResultView

  3. 现在将您的 DataContext 设置CustomerSerachDetailView为它的 VM,类似于如何CustomerSerachResultsView链接到它的 VM(使用 xaml 中的 DataContext Binding 猜测ViewModelLocator

  4. 现在你可以把你的命令放在Button's of CustomerSerachDetailViewjust as<Button Command="{Binding SaveCommand}" ...

  5. 最后,因为SelectedRow不再是DataContextCustomerSerachDetailsView,您的 FirstName、Lastname、Address 的绑定似乎都将停止工作。

我们有很多选择来解决这个问题。

首先是在每个 Binding 中使用一个 RelativeSource FindAncestor 绑定,通过我们在获取适当字段之前创建CustomerSerachDetailsView的 CurrentCustomer DP( )指向那里。DependencyProperty

例如:

<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />

现在,如果您有多个属性,这很快就会开始变得烦人。所以然后选择一个共同的祖先(比如这些TextBlocks 中的 3 个被分组在 a 下)并通过与 ^^ ​​的类似绑定StackPanel将它的 DataContext 作为元素应用。CurrentCustomer现在,StackPanel孩子的 DataContext 将成为 Customer 元素,因此在他们的每个绑定中,您不必执行整个 RelativeSource 的事情,只需提及{Binding Path=FirstName}等等。

而已。现在你得到了两个视图,它们有各自的 VM 和一个 Model( Customer),每个视图都有各自的任务。

太好了,我们完成了吗?还没有出错。

虽然方法 1比我们开始的方法更好,但它仍然只是“meh”。我们可以做得更好。

方法二

MVVMLight 有一个Messenger类,它允许您以弱依赖格式在不同类之间进行通信。如果你还没有,你需要调查一下。

那么我们该怎么做Messenger呢?

很简单:

  1. 在 in 的 setter 中SelectedRowCustomerSearchResultsViewModel我们将发送一条带有新传入valueto的消息CustomerSearchDetailsViewModel

  2. 现在CustomerSearchResultsViewModel我们将添加一个属性CurrentCustomer并将这个传入值分配给它。

  3. CustomerSerachDetailsView我们不再创建DP。这意味着我们不再在from中设置SelectedRow任何东西(DataContext 或 DP)(甜少的工作:))CustomerSerachDetailsViewCustomerSearchResultsView

  4. 至于我们分配DataContextCustomerSerachDetailsView方式或绑定方式Button.Command- 它们与方法 1相同

  5. 最后是实际的“FirstName”等绑定的。那么现在CurrentCustomerCustomerSearchDetailsViewModel. 所以绑定到它就像按钮绑定到它的命令一样

^^ 现在可以正常工作了DataContext,因为它TextBlock是虚拟机并且属性CurrentCustomer存在于其中。

于 2013-07-10T23:32:37.113 回答