【问题标题】:How to add mutiple ViewModels to user controls in main window如何在主窗口中向用户控件添加多个 ViewModel
【发布时间】:2017-06-05 14:06:03
【问题描述】:

这是我第一次尝试实现 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 和导航正确加载视图,但是我有一个命令在加载时运行并且它不执行。这只是填充一个列表视图。我知道它可以工作,因为如果我删除窗口的数据上下文并将其设置为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;
}

【问题讨论】:

  • 请展示这些数据模板是如何在 XAML 中使用的。您不需要在模板内的视图上显式设置 DataContext;它将继承数据模板的 DataContext。 DataContext="{Binding VM:CustomerVM}" 是什么意思?你想给它一个类型吗?
  • 感谢您的更新。 ContentControl 从哪里获得Content
  • 我已经更新了关于如何加载视图的问题。CustomerVM 是客户用户控制视图的 ViewModel。
  • 我会更详细地更新问题
  • 这方面的学习曲线非常陡峭。请参阅我的更新:就目前而言,您所做的一切都会奏效,但MainVM 如何与孩子们互动?它甚至找不到它们。我的更新演示了一个解决该问题的简单设计。

标签: c# wpf xaml mvvm


【解决方案1】:

我认为这里发生了几件事。我们将从一开始对我来说最清楚的那个开始。

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 中为每个视图提供一个 &lt;DataContext&gt; 元素:

<DataContext>
    <VM:CustomerVM />
</DataContext>

但是主视图模型如何与其子视图模型交互?一种解决方案是使用 MVVM 框架,该框架添加了一个拜占庭式“定位器”,通过一堆杂乱无章的解决方法和变通方法,半途而废地解决了这个问题。 Much effort is spent satisfying the framework's demands

这种设计让视图负责一切,但视图没有逻辑。他们没有业务负责。您的应用程序的“大脑”被分成十几个相互隐藏的小块。

另一种方式是“以视图模型为中心的设计”:将您的应用程序设计为一组视图模型。子视图模型是父视图模型的属性。 MainVM 可以轻松找到它的子 SupplierVM,因为 MainVM 创建并拥有它。视图模型不是悬挂在视图中的孤立附属物,而是视图模型的孤立附属物——这绝对没问题,因为无论如何视图几乎都坐在那里。

通过这种方式,使用 WPF 的一切都变得更容易。

这里有三个视图模型:一个父级 MainVM 和两个子级 CustomerVMSupplierVM。在主视图中,您希望一次显示一个或另一个子级。

所以我们会给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 中做一些有效的事情并不总是那么容易),但这种方式更容易且更传统。

【讨论】:

  • 我现在实现了这个梦想,帮助我更多地了解 MVVM,再次感谢。如果您有任何在线博客或教程,我会很感兴趣,因为您的方法和解释很清楚。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多