如何将多个ViewModel添加到主窗口的用户控件中

3

这是我第一次尝试实现MVVM模型,所以预先道歉。我有一个主窗口,它有自己的ViewModel。然后,主窗口有三个用户控件,根据导航菜单中选择的内容进行加载。但是数据上下文没有更改为正确的视图模型。

MainWindow.xaml

<Window.DataContext>
    <VM:MainVM></VM:MainVM>
</Window.DataContext> 

<Window.Resources>
    <DataTemplate x:Key="View1Template" DataType="{x:Type VM:CustomerVM}">
        <View:Customers  DataContext="{Binding VM:CustomerVM}" />
    </DataTemplate>

    <DataTemplate x:Key="View2Template" DataType="{x:Type VM:SuppliersVM}">
        <View:Suppliers DataContext="{Binding VM:Suppliers}"/>
    </DataTemplate>
</Window.Resources>

<ContentControl  Margin="0,135,0,10" Grid.Column="1">
    <ContentControl.Style>
        <Style TargetType="{x:Type ContentControl}">
            <Setter Property="ContentTemplate"  Value="{StaticResource View1Template}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding SwitchView}" Value="0">
                    <Setter Property="ContentTemplate" Value="{StaticResource View1Template}" />
                </DataTrigger>
                <DataTrigger Binding="{Binding SwitchView}" Value="1">
                    <Setter Property="ContentTemplate" Value="{StaticResource View2Template}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </ContentControl.Style>
</ContentControl>

这个页面有自己的ViewModel,导航能够正确加载视图,但是我有一个在加载时运行的命令,但它没有执行。这个命令只是填充一个列表视图。我知道它可以工作,因为如果我删除窗口的DataContext并将其设置为CustomerVM,则列表视图将被填充,但导航不再起作用,因为MainVM已被删除。 Customer.xaml
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LoadCustomersCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <TextBox Tag="Search Customers" x:Name="searchTextBox" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" HorizontalAlignment="Left" Height="40" Margin="10,9,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="340" Padding="5,6,0,0" FontSize="16"/>
    <Label Content="{Binding Path=SearchText}" Margin="430,0,436,263" />
    <ListView ItemsSource="{Binding Customers}" x:Name="customerListBox" Margin="10,57,10,10"  AlternationCount="2"   >
        <ListView.View>
            <GridView>
                <GridViewColumn Header="ID" Width="Auto" DisplayMemberBinding="{Binding id}" />
                <GridViewColumn Header="NAME" Width="Auto" DisplayMemberBinding="{Binding name}"  />
                <GridViewColumn Header="ADDRESS" Width="Auto" DisplayMemberBinding="{Binding address1}" />
                <GridViewColumn Header="ADDRESS 2" Width="150" DisplayMemberBinding="{Binding address2}" />
                <GridViewColumn Header="TOWN" Width="150" DisplayMemberBinding="{Binding town}" />
                <GridViewColumn Header="COUNTY" Width="150" DisplayMemberBinding="{Binding county}" />
                <GridViewColumn Header="POSTCODE" Width="150" DisplayMemberBinding="{Binding postcode}" />
                <GridViewColumn Header="PHONE" Width="150" DisplayMemberBinding="{Binding phone}" />
                <GridViewColumn Header="EMAIL" Width="150" DisplayMemberBinding="{Binding email}" />
            </GridView>
        </ListView.View>   
    </ListView>
</Grid>

CustomerVM.cs

private string _searchText;
private readonly ObservableCollection<Customer> _customers = new ObservableCollection<Customer>();

public string Title = "Customers";

public string SearchText
{
    get { return _searchText; }
    set
    {
        _searchText = value;
        RaisePropertyChangedEvent("SearchText");
    }
}

public IEnumerable<Customer> Customers
{
    get { return _customers; }
}

public ICommand LoadCustomersCommand
{
    get { return new DelegateCommand(LoadCustomers); }
}

public void LoadCustomers()
{
    Customer cus = new Customer { id = 1, name = "sam" };
    _customers.Add(cus);

}

MainVM.cs

public ICommand NavigationClick
{
    get { return new DelegateCommand(Navigate); }
}

public void Navigate()
{        
    SwitchView = 1;
}

你尝试将内容设置为什么了吗?另外,你提到了一个列表视图,但我没有看到任何涉及列表视图的迹象,所以我担心你在这里做了很多事情,而我甚至无法猜测。 - 15ee8f99-57ff-4f92-890c-b56153
1
我会在问题中添加更多细节。 - Bish25
设计实例是在设计时创建的实例,以便设计人员可以提供有关视图模型属性的智能感知帮助。在运行时它不存在。我会立即更新我的答案来解决这个问题。 - 15ee8f99-57ff-4f92-890c-b56153
@edplunkett 我现在感到非常尴尬,我在用户控件中添加了<UserControl.DataContext><ViewModel:CustomerVM></ViewModel:CustomerVM></UserControl.DataContext>,现在它可以工作了。这是因为我只在设计实例中声明了它。 - Bish25
1
这方面的学习曲线相当陡峭。请看我的更新:你所做的可以运行,但是MainVM如何与子级交互呢?它甚至找不到它们。我的更新演示了一个简单的设计来解决这个问题。 - 15ee8f99-57ff-4f92-890c-b56153
显示剩余8条评论
1个回答

4
我认为这里有几件事情需要解释一下。首先,DataContext是一个对象,通常是一个视图模型类的实例。而DataTemplate用于显示视图模型类的实例。它的DataContext会被包含在其中的控件继承。这可能是你所需要的:
<Window.Resources>
    <DataTemplate DataType="{x:Type VM:CustomerVM}">
        <View:Customers />
    </DataTemplate>

    <DataTemplate DataType="{x:Type VM:SuppliersVM}">
        <View:Suppliers />
    </DataTemplate>
</Window.Resources>

其次,您需要使用一个实际的子视图模型实例来填充ContentControl。一种方法是在创建视图时,在XAML中为每个视图提供一个元素来创建视图模型实例:
<DataContext>
    <VM:CustomerVM />
</DataContext>

但主要视图模型如何与其子项进行交互呢?一种解决方案是使用一个MVVM框架,添加一个拼凑而成的“定位器”来解决这个问题。但需要花费大量精力来满足框架的需求。(点击此处查看详情) 该设计将所有控制权都交给了视图,但视图没有逻辑。它们不应该负责所有事情。你的应用程序的“大脑”被分成了十几个相互隔离的小部分。
另一种方式是“视图模型中心化设计”:将您的应用程序设计为一组视图模型。子视图模型是父视图模型的属性。MainVM 可以轻松地找到它的子SupplierVM,因为MainVM创建并拥有它。视图模型不再是从视图悬挂的孤立附属物,而是视图模型的孤立附属物——这完全没问题,因为视图基本上只是坐在那里。
在WPF中,以这种方式处理一切都更容易。此处有三个视图模型:一个父视图模型MainVM和两个子视图模型CustomerVM和SupplierVM。在主视图中,您希望一次显示一个子视图模型。
因此,我们将在MainVM中添加每个子项的实例:
public MainVM()
{
    Customer = new CustomerVM();
    Supplier = new SupplierVM();
}

public CustomerVM Customer {
    get { return _customerVM; }
    private set { _customerVM = value; }
}
public SupplierVM Supplier {
    get { return _supplierVM; }
    private set { _supplierVM = value; }
}

public INotifyPropertyChanged _selectedChild;
public INotifyPropertyChanged SelectedChild {
    get { return _selectedChild; }
    set {
        if (value != _selectedChild) {
            _selectedChild = value;
            //  I don't know how you raise PropertyChanged; if it doesn't look 
            //  like this, let me know. 
            OnPropertyChanged();
        }
    }
}

public void Navigate()
{        
    SelectedChild = Customer;
}

在XAML中,内容控件被大大简化。无论您将什么放在“SelectedChild”中都将显示在内容控件中。它如何知道使用哪个模板?
简单:我们从上面的数据模板中删除了“x:Key”属性,但保留了“DataType”属性。这使它们成为所谓的“隐式数据模板”:只要它们在范围内,任何需要显示其中一种类型对象的ContentControl都会自动使用它们。在此Window中的任何位置,如果您告诉XAML“这是一个CustomerVM;向用户展示它”,XAML将使用上述两个数据模板中的第一个来显示它。
<ContentControl 
    Content="{Binding SelectedChild}"
    Margin="0,135,0,10" 
    Grid.Column="1"
    />

你可以取消 SwitchView 属性。你所做的肯定有效(而且正如你已经发现的那样,在 XAML 中做一些有效的事情并不总是容易的),但这种方法更简单更常规。

1
我已经实现了这个,现在它完美地运行了,并帮助我更好地理解了MVVM,再次感谢。如果您有任何在线博客或教程,我会很感兴趣,因为您的方法和解释非常清晰。 - Bish25

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接