【Prism系列】Region的用法

前言

    Prism中的Region可以和WPF中提供的Frame进行类比,Frame通过加载不同的Page达到界面导航的效果。Region也有相同的作用,不过Region有Psrim框架以及容器的加持,能够支持更细致化的导航控制,更轻松的传参, 更方便的生命周期管理,以及提供更加松的耦合,等等。Region的整个套路和上一篇文章《Prism子窗口实现》有着很多共同之处,大家可以对照学习。

注册与注入

和上篇一样,我们准备几个用户控件用来充当导航的内容。同样准备的内容,我们可以提前注册到容器中。上篇中我们使用:containerRegistry.RegisterDialog<MyDialog>(); 注册了子窗口,这次我们使用 containerRegistry.RegisterForNavigation<MyDialog>();  将MyDialog整个控件注册成导航需要的界面。不过这次为了导航效果再准备一个叫MyView的控件。

讲《Prism子窗口实现》的时候,Prism为我们准备了IDialogService管理窗口。同样的Prism也为我们提供了一个叫IRegionManager的对象管理Region。这里我们可以直接注入:

[Dependency]
public IRegionManager regionManager { get; set; }

前台区域划分

接下来,我们需要再前台画出一片区域,并且給这片区域起个名字:(xmlns:prism="http://prismlibrary.com/"

<ContentControl Grid.Row="1" Grid.Column="1" prism:RegionManager.RegionName="MainRegion"/>

这貌似就是给ContentControl添加了一个附加属性啊?为啥是ContentControl?这里涉及到Region适配器的相关内容,我这里暂且不表。现在只需理解为,ContentControl所在的这篇区域就是划分出来的Region,并且名字叫做MainRegion。

这里需要注意一个问题,Region区域需要定义到顶层Windows,如果定义到子界面比如Page中,会导致区域名称识别不到(即使通过RegisterViewWithRegion注册,切换界面也会有切换不了的问题)。

其实Region只需要存在于主界面,就能解决我们的所有需求,无非是多划分几个区域而已,并不需要嵌套。

关系建立

现在内容准备好了,区域也划分好了,现在需要讲内容和区域建立关系。

regionManager.RequestNavigate("MainRegion", "MyDialog");

MainRegion是区域的名字,MyDialog是要显示内容的类名称。

下面提供完整的测试代码:

<ContentControl Grid.Row="1" Grid.Column="1" prism:RegionManager.RegionName="MainRegion"/>
<StackPanel Grid.Row="1">
    <Button Content="界面1" Command="{Binding BtnCommand}" CommandParameter="MyDialog"/>
    <Button Content="界面2" Command="{Binding BtnCommand}" CommandParameter="MyView"/>
</StackPanel>
[Unity.Dependency]
public IRegionManager regionManager { get; set; }
public Region的用法ViewModel()
{
    BtnCommand = new DelegateCommand<string>(Do);
}
void Do(string str)
{
    regionManager.RequestNavigate("MainRegion", str);
}

测试效果:

 Region适配器

首先要说明的是,RequestNavigate的是单例模式,相同的类只会往区域里添加一次。为了证明这一点,我们换一个适配器看看,之前我们用的是,ContentControl。现在换成ItemsControl试试:

<ScrollViewer Grid.Column="2" >
    <ItemsControl  prism:RegionManager.RegionName="MainRegion"/>
</ScrollViewer>

其余的不变,效果如下:

最明显的区别是,当 ContentControl换成ItemsControl后,切换界面,变成了添加了两个界面。

这个就是适配器带来的区别。

其实要注意是,不过切换多少次,最多只能添加两个。这就是RequestNavigate的单例的特性。

如果像添加多个,需要将RequestNavigate换成AddToRegion:

regionManager.AddToRegion("MainRegion", str);

效果如下:

 从这里我们可以看到,RequestNavigate是用于单个界面的切换,通常和ContentControl配合使用。而AddToRegion是用于界面的添加,或者说是多个界面的嵌入,通常和ItemsControl配合使用。

其实将区域和内容建立关系的函数,除了RequestNavigate和AddToRegion,其实还有一个RegisterViewWithRegion。如果换成RegisterViewWithRegion,你会发现这个效果和AddToRegion是一模一样的。它和AddToRegion的区别在于,它额外包含注册部分,也就是之前再App中注册的部分可以省略掉:

containerRegistry.RegisterForNavigation<MyDialog>();
containerRegistry.RegisterForNavigation<MyView>();

适配器的种类

  • ContentControlRegionAdapter: ContentControl
  • ItemsControlRegionAdapter: ItemsControl
  • SelectorRegionAdapter
    - ComboBox
    - ListBox
    - Ribbon
    - TabControl

也就是说,并不是任何的控件,都能最为区域,符合上述适配器的要求控件才能作为Region。

大家可以尝试不用控件作为区域会是什么效果,比如TabControl。后续随着研究的深入,我再进行内容的补充吧。

prism还停供了接口,让我们可以自定义适配器(暂时不去研究了)。

Region的生命周期

        通过管理的界面,是有IOC加持的,所以有生命周期的概念,我们知道,每次当Page切换时,Page其实经历了Unload的和Load的过程,之前界面修改的内容,切换后都会被重置。

但是Region默认会被保存到IOC里(本质在内存中保存),所以切换界面,界面不会被重置。

但是,如果我们也可以让其切换后重置。只需要让控件界面对应的ViewModel实现接口IRegionMemberLifetime即可。这个接口里只有一个KeepAlive变量,将其置为false即可。

public bool KeepAlive => false;
//或者写成:
public bool KeepAlive { get; } = false;

导航确认

导航确认,就是跳转时可能会有几个步骤,prism框架提供几个回调,让我们的代码可以参与其中。让控件界面对应的ViewModel实现接口IConfirmNavigationRequest:

当我们调用RequestNavigate时,离开当前页面A会调用A的ConfirmNavigationRequest,确认是否离开,如确认离开,则继续调用A的OnNavigatedFrom,去到另外一个页面B,会调用B的IsNavigationTarget,表示返回一个新的界面还是IOC里已经有的,然后继续调用OnNavigatedTo表示已经来到B界面。

#region 导航确认
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
    // 确认是否离开当前视图
    bool result=true;

    //if (MessageBox.Show("Do you to navigate?", "Navigate?", MessageBoxButton.YesNo) == MessageBoxResult.No)
    //{ result = false; } 
    //else
    //{ result = true;  }

    continuationCallback(result);
}


public void OnNavigatedTo(NavigationContext navigationContext)
{
    //来到当前试图
    var value = navigationContext.Parameters["value"];
}

public bool IsNavigationTarget(NavigationContext navigationContext)
{
    // 当切换到本界面时:
    // 当返回True的时候,返回容器里面的view
    // 当返回 False的时候,返回一个新的view
    // 和 KeepAlive时相同的意思
    return ture;
}

public void OnNavigatedFrom(NavigationContext navigationContext)
{
    // 离开当前视图
}
#endregion

这里说明几个点:

ConfirmNavigationRequest:里面有个回调函数,调用时如果传true,表示可以跳转。如果为false表示不可以跳转。如果不调用这个回调,也无法跳转。

IsNavigationTarget:如果你是为了切换界面,这里IsNavigationTarget永远返回true,不要返回false。返回false会导致新的界面产生旧的还在,这样随着界面切换的次数增加,IsNavigationTarget被框架调用的次数随着增加,内存也变大。

如果你想返回一个新的界面,应该使用KeepAlive=>false ,这样不会导致该问题!

导航传参

在调用regionManager.RequestNavigate的时候,可以多传一个参数:

NavigationParameters para = new NavigationParameters();
para.Add("key", "any");
regionManager.RequestNavigate("MainRegion", "MyView", para);

那么在实现接口IConfirmNavigationRequest的函数里都有一个参数: navigationContext,

这个参数中就会包含RequestNavigate传进来的参数对象了:

最后,欢迎大家,点赞+关注。

posted @   宋桓公  阅读(759)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示