WPF Stylet可以如何實現(xiàn)導航功能?

前言
本文是學習Stylet中導航Demo的總結,希望對你有所幫助。
Demo所在的位置:

先看一下導航的效果:
首頁

通過上面導航到Page 2:

通過Page1導航到Page2:

Stylet是如何實現(xiàn)導航的?
先來看一下頁面布局:

一共有ShellView、HeaderView、Page1View與Page2View一共四個View。
ShellView的xaml如下:
<Window x:Class="Stylet.Samples.NavigationController.Pages.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Stylet.Samples.NavigationController.Pages"
mc:Ignorable="d"
Title="Navigation Controller sample" Height="450" Width="800"
xmlns:s="https://github.com/canton7/Stylet"
d:DataContext="{d:DesignInstance local:ShellViewModel}">
<DockPanel>
<ContentControl DockPanel.Dock="Top" s:View.Model="{Binding HeaderViewModel}"/>
<ContentControl s:View.Model="{Binding ActiveItem}"/>
</DockPanel>
</Window>
頁面的上部分通過s:View.Model="{Binding HeaderViewModel}"綁定到了HearView。
下部分通過s:View.Model="{Binding ActiveItem}"綁定到了激活項的View。
這里你可能會感到疑惑,ActiveItem這個屬性是哪里來的呢?
ActiveItem這個屬性是在ConductorBaseWithActiveItem<T>中定義的:

ShellViewModel繼承 Conductor<IScreen>, Conductor<IScreen>繼承 ConductorBaseWithActiveItem<T>。
這里你就把ActiveItem理解成導航激活的那個ViewModel就行了,這個例子中要么是Page1ViewModel要么就是Page2ViewModel。
現(xiàn)在來看一下NavigationController:
public class NavigationController : INavigationController
{
private readonly Func<Page1ViewModel> page1ViewModelFactory;
private readonly Func<Page2ViewModel> page2ViewModelFactory;
public INavigationControllerDelegate Delegate { get; set; }
public NavigationController(Func<Page1ViewModel> page1ViewModelFactory, Func<Page2ViewModel> page2ViewModelFactory)
{
this.page1ViewModelFactory = page1ViewModelFactory ?? throw new ArgumentNullException(nameof(page1ViewModelFactory));
this.page2ViewModelFactory = page2ViewModelFactory ?? throw new ArgumentNullException(nameof(page2ViewModelFactory));
}
public void NavigateToPage1()
{
this.Delegate?.NavigateTo(this.page1ViewModelFactory());
}
public void NavigateToPage2(string initiator)
{
Page2ViewModel vm = this.page2ViewModelFactory();
vm.Initiator = initiator;
this.Delegate?.NavigateTo(vm);
}
}
看一下INavigationController:
public interface INavigationController
{
void NavigateToPage1();
void NavigateToPage2(string initiator);
}
首先解決一個疑問,這里為什么使用private readonly Func<Page1ViewModel> page1ViewModelFactory;而不是直接使用Page1ViewModel呢?
我們知道在C#中Func<Page1ViewModel>表示一個沒有參數(shù),返回值為Page1ViewModel的委托。
再看看Bootstrapper中的ConfigureIoC方法:

這樣寫的目的就是不是一開始就將Page1ViewModel與Page2ViewModel注入進來,而是在使用的時候才注入進來。
我們發(fā)現(xiàn)在NavigationController中具體實現(xiàn)導航是通過INavigationControllerDelegate接口實現(xiàn)的,讓我們再來看看這個接口:
public interface INavigationControllerDelegate
{
void NavigateTo(IScreen screen);
}
回到ShellViewModel,我們發(fā)現(xiàn)它實現(xiàn)了這個接口。

來看下它的實現(xiàn):
public void NavigateTo(IScreen screen)
{
this.ActivateItem(screen);
}
使用的是 Conductor<T>中的ActivateItem方法:

當我們從頁面1導航到頁面2時:

由于要導航去的Page2ViewModel不是當前的激活項Page1ViewModel,就會來到ChangeActiveItem方法:

關閉之前的激活項,設置新的激活項。
就成功導航到Page2ViewModel了,然后根據(jù)Page2ViewModel就會找到Page2View了,這樣就成功實現(xiàn)導航功能了。
最后再來看一下有一個循環(huán)依賴問題:

這里存在一個循環(huán)依賴關系:ShellViewModel -> HeaderViewModel -> NavigationController -> ShellViewModel。
如果直接在NavigationController的構造函數(shù)中注入ShellViewModel就會引發(fā)這個循環(huán)依賴問題。
作者通過在構建 NavigationController 后,再將 ShellViewModel 賦值給它的方式來打破這一循環(huán)依賴。
最后
Stylet導航功能的實現(xiàn)主要是通過Conductor<T>實現(xiàn)的。
從作者的這個示例中學習了如何使用Stylet實現(xiàn)一個導航應用,還是學習到了很多知識的,感謝作者的付出!!

浙公網(wǎng)安備 33010602011771號