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



在某种程度上,我一直在尝试使用层次结构和导航来遵循此 MVVM 教程:

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

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

所以有人可以帮助我如何在没有 MVVM Light 或 MVVM Cross 等工具的帮助下,使用普通 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;
}
}
}
}

最后这是我的帮助程序可绑定类:

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 类,事实证明,这个解决方法非常适合我的问题,因为 UWP 没有隐式绑定。 因此,如果你正在探索 UWP,则没有隐式绑定,则必须使用显式绑定才能使此特定问题正常工作。

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

首先,我创建了一个模板类,以便我可以检索有助于我使用 Explicit Binding 的属性:

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

然后我会使用一个集合,但我会在这个上使用立面图案,我善良的爱情图案是有趣和优雅的。

public class TemplateCollection : Collection<Template>
{
}

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

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);
}
}

此类可以在这里找到:如何将视图与视图模型或多个 ViewModel 的数据模板相关联?

只是我不喜欢偷学分,但我确实从这个答案和我的搭档 sugesstion MLavoie 中学到了东西。

所以这是我的修复视图,多亏了这一点,我能够使用导航并创建了一个分层 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 编码。

最新更新