MVVM – UWP 的层次结构和导航隐式绑定

MVVM – Hierarchies & Navigation Implicit Binding for UWP

我一直在尝试使用层次结构和导航来学习这个 MVVM 教程:

https://www.tutorialspoint.com/mvvm/mvvm_hierarchies_and_navigation.htm

到目前为止,我已经完成了本教程的大部分内容,但是当涉及到 UWP 时,隐式绑定似乎不适用于 UWP,因此我无法复制本教程,因为即使我使用了 x:DataType 与 x:Key 编译器要求 x:key 属性以便将视图与 viewmodel 绑定,我得到的只是我的 viewmodel 的全名,而不是能够看到实际内容。

有人可以帮助我如何在不借助 MVVM Light 或 MVVM Cross 等工具的情况下使用 Plain MVVM 模式在 UWP 中正确使用层次结构。

我将把我目前拥有的 UWP 应用程序代码留给您:

<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.

We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
    <viewmodel:MainPageViewModel/>
</Page.DataContext>

<Page.Resources>
    <DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
        <views:CustomerListView/>
    </DataTemplate>

    <DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
        <views:OrderView/>
    </DataTemplate>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid x:Name="NavBar"
          Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>


        <Button Content="Customers"
                Command="{Binding NavCommand}"
                CommandParameter="customers"
                Grid.Column="0"
                Grid.Row="0"/>

        <Button Content="Orders"
                Command="{Binding NavCommand}"
                CommandParameter="orders"
                Grid.Column="2"
                Grid.Row="0"/>
    </Grid>

    <Grid x:Name="MainContent"
          Grid.Row="1">
        <ContentControl Content="{Binding CurrentViewModel}"/>
    </Grid>      
</Grid>

如您所见,主要问题可能出在我的主页上。我猜我绑定的资源

因为那行代码无法访问我视图的实际内容。

这是我的主视图的视图模型:

namespace MVVMHeirarchiesDemo.ViewModel
{
    /*Derive all of your ViewModels from BindableBase class.*/
    public class MainPageViewModel : BindableBase
    {
        public MainPageViewModel()
        {
            NavCommand = new MyCommand<string>(OnNavigation);
        }

        private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();

        private OrderViewModel _orderViewModel = new OrderViewModel();

        private BindableBase _currentViewModel;

        public BindableBase CurrentViewModel
        {
            get
            {
                return _currentViewModel;
            }

            set
            {
                SetProperty(ref _currentViewModel, value);
            }
        }

        public MyCommand<string> NavCommand { get; private set; }

        private void OnNavigation(string destination)
        {
            switch (destination)
            {
                case "orders":
                    {
                        CurrentViewModel = _orderViewModel;
                        break;
                    }
                case "customers":
                default:
                    CurrentViewModel = _customerListViewModel;
                    break;
            }
        }
    }
}

最后这是我的可绑定助手 class:

namespace MVVMHeirarchiesDemo
{
    /*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
     * and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
     * Following is the implementation of BindableBase class.*/
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
        {
            if (object.Equals(member, val))
                return;

            member = val;
            OnPropertyChanged(propertyName);
        }
    }
}

我希望有人能帮我解决这个问题。

好吧,我花了一段时间,但我能够理解如何使用 DataTemplateSelector class,事实证明,这项工作非常适合我的问题,因为 UWP 没有隐式绑定。 因此,如果您正在探索 UWP,则没有 IMPLICIT BINDING,您必须使用显式绑定才能解决此特定问题。

所以这是我的解决方案,我仍然使用我的 BindableBase,因为它帮助我为我正在调用的视图选择合适的 ViewModel,因此无需更改它。

firstable 我已经创建了一个模板 class 这样我就可以检索有助于我使用 ExplicitBinding 的属性:

public class Template
{
    public string DataType { get; set; }

    public DataTemplate DataTemplate { get; set; }
}

然后我将使用一个集合,但我将在这个上使用一个外观模式,我喜欢模式有趣而优雅。

public class TemplateCollection : Collection<Template>
{
}

然后我用另一个答案 class 帮助我重用它来解决我的问题:

public class MyDataTemplateSelector : DataTemplateSelector
{
    public TemplateCollection Templates { get; set; }

    private ICollection<Template> _templateCache { get; set; }

    public MyDataTemplateSelector()
    {

    }

    private void InitTemplateCollection()
    {
        _templateCache = Templates.ToList();
    }

    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
    {
        if (_templateCache == null)
        {
            InitTemplateCollection();
        }

        if (item != null)
        {
            var dataType = item.GetType().ToString();

            var match = _templateCache.Where(m => m.DataType == dataType).FirstOrDefault();

            if (match != null)
            {
                return match.DataTemplate;
            }
        }

        return base.SelectTemplateCore(item, container);
    }
}

这个 class 可以在这里找到:

只是我不喜欢偷学分,但我确实从这个答案和我的合作伙伴 MLavoie 的建议中学到了东西。

所以这是我的 Fix 视图,因此我能够使用导航并创建了一个分层 MVVM 模式。

<Page
x:Class="MVVMHeirarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHeirarchiesDemo"
xmlns:dtempsltor="using:MVVMHeirarchiesDemo.Templates"
xmlns:views="using:MVVMHeirarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHeirarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.

We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
    <viewmodel:MainPageViewModel/>
</Page.DataContext>

<Page.Resources>
    <dtempsltor:TemplateCollection2 x:Key="templates">
        <dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.CustomerListViewModel">
            <dtempsltor:Template.DataTemplate>
                <DataTemplate x:DataType="viewmodel:CustomerListViewModel">
                    <views:CustomerListView/>
                </DataTemplate>
            </dtempsltor:Template.DataTemplate>
        </dtempsltor:Template>
        <dtempsltor:Template DataType="MVVMHeirarchiesDemo.ViewModel.OrderViewModel">
            <dtempsltor:Template.DataTemplate>
                <DataTemplate x:DataType="viewmodel:OrderViewModel">
                    <views:OrderView/>
                </DataTemplate>
            </dtempsltor:Template.DataTemplate>
        </dtempsltor:Template>
    </dtempsltor:TemplateCollection2>
    <dtempsltor:MyDataTemplateSelector x:Key="MyDataTemplateSelector" 
                                       Templates="{StaticResource templates}"/>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Grid x:Name="NavBar"
          Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>


        <Button Content="Customers"
                Command="{Binding NavCommand}"
                CommandParameter="customers"
                Grid.Column="0"
                Grid.Row="0"/>

        <Button Content="Orders"
                Command="{Binding NavCommand}"
                CommandParameter="orders"
                Grid.Column="2"
                Grid.Row="0"/>
    </Grid>

    <Grid x:Name="MainContent"
          Grid.Row="1">
        <ContentControl ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
                        Content="{Binding CurrentViewModel}"/>
    </Grid>      
</Grid>

感谢那些帮助我启发自己的人这很棒,能够通过一些提示解决它并且能够解决它感觉很好,我有点喜欢用 C# 和 Xaml .