C# WPF Prsim PrsimOutlook学习记录(二)

本文介绍了如何在Prsim和WPF项目中利用PrsimOutlook进行导航,通过XamOutlookBar控制右侧ContentRegion视图切换,使用CompositeCommand处理点击事件,以及IOutlookBarGroup接口的运用,展示了ViewModel的管理和依赖注入的过程。
摘要由CSDN通过智能技术生成

最近在学习 Prsim 和WPF,在油管上找到了一个 PrsimOutlook 项目,作者是 Brian Lagunas ,是当年 Prsim 搬家到GitHub时交给社区的三位贡献者之一.

照着视频学习加上有源代码,视频作者就是Prsim的作者之一,对Prsim相当的了解,视频思路清晰,代码水平高,值得初学习.

以往文章链接

C# WPF Prsim PrsimOutlook学习记录(一)

相关链接

视频二 Prism Navigation 学习记录

注:学习记录只是思路的整理,并没有把所有源码都贴在这里,具体源码可以看上方的链接下载

这节视频主要讲了导航相关的东西,主要是点击 XamOutlookBar 中的内容,将右边 ContentRegion中View内容,切换成对应的内容.

在这里插入图片描述
首先让我们先看 MailGroup, 这里面主要是想做邮件的发送,删除,接收功能. Outlook在这个地方做的是树形的结构.
所以首先在 MailGroup.xaml 建立树节点

<ig:XamDataTree x:Name="_dataTree" ItemsSource="{Binding Items}">
            <ig:XamDataTree.GlobalNodeLayouts>
                <ig:NodeLayout Key="GlobalLayout" TargetTypeName="NavigationItem" DisplayMemberPath="Caption"></ig:NodeLayout>                
            </ig:XamDataTree.GlobalNodeLayouts>
        </ig:XamDataTree>

这里需要指定 TargetTypeName 树节点的类名和 DisplayMemberPath 显示的名称.

这里是业务相关的信息,所以创建了 .NET 类库项目
在这里插入图片描述
创建 NavigationItem.cs类,一个标准的树型结构

namespace PrismOutlook.Business
{
    public class NavigationItem
    {
        public string  Caption { get; set; }

        public string NavigationPath { get; set; }

        public ObservableCollection<NavigationItem> Items { get; set; }

        public NavigationItem()
        {
            Items = new ObservableCollection<NavigationItem>();
        }
    }
}

再创建一个 MainGroupViewModel.cs类, 由于 MainGroup.xaml是放在Menus目录下的,不是放在Prsim默认的 关联路径,所以需要在 MailModule.cs中使用 ViewModelLocationProvider 注册一下

public void RegisterTypes(IContainerRegistry containerRegistry)
{
      ViewModelLocationProvider.Register<MailGroup, MainGroupViewModel>();
}

MailGroup 里面有一个binding Items,这里在 ViewModel里写一下

 private ObservableCollection<NavigationItem> _items;
        public ObservableCollection<NavigationItem> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }

然后将树赋值给Items,Load到界面上


public MainGroupViewModel()
        {
            GenerateMenu();            
        }

void GenerateMenu()
        {
            Items = new ObservableCollection<NavigationItem>();
            var root = new NavigationItem() { Caption = "Personal Folder", NavigationPath = "MailList" };
            root.Items.Add(new NavigationItem() { Caption = "Inbox", NavigationPath = "" });
            root.Items.Add(new NavigationItem() { Caption = "Deleted", NavigationPath = "" });
            root.Items.Add(new NavigationItem() { Caption = "Sent", NavigationPath = "" });
            Items.Add(root);
        }

现在,树结构已经创建好了。
下面要做的是点击左边对应的结构,右边的视图需要做对应的切换。
在这里插入图片描述

下面先做 点击Mail 和Contacts,导航到对应的View

在MainWindow.xaml中

<ig:XamOutlookBar 
                prism:RegionManager.RegionName="{x:Static core:RegionNames.OutlookGroupRegion}"
                SelectedGroupChanged="XamOutlookBar_SelectedGroupChanged"
                DockPanel.Dock="Left" Width="200"></ig:XamOutlookBar>

这里使用Code Behind的方式,建立 XamOutlookBar_SelectedGroupChanged事件.
这里也告诉我们不要什么都用MVVM模式

在 MainWindowViewModel.cs中 写入以下代码
再次感慨 Prsim 很方便, 从容器中拿到 IRegionManager实例
调用 RequestNavigate,导航到指定的View

		 private readonly IRegionManager _regionManager;

         public MainWindow(IRegionManager regionManager)
         {
             InitializeComponent();
             Infragistics.Themes.ThemeManager.ApplicationTheme = new Office2013Theme();
             this._regionManager = regionManager;
         }

         private void XamOutlookBar_SelectedGroupChanged(object sender, RoutedEventArgs e)
         {
             var group =  ((XamOutlookBar)sender).SelectedGroup as IOutlookBarGroup;
             if (group != null)
             {
                 // TODO navigate to group.DefaultNavigationPath
                 _regionManager.RequestNavigate(RegionNames.ContentRegion, group.DefaultNavigationPath);

             }
         }

当然,调用这个请求导航前,必须要注册一下.

ContactsModule.cs 中

public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
        }

MailModule.cs 中

 public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            ViewModelLocationProvider.Register<MailGroup, MainGroupViewModel>();
            containerRegistry.RegisterForNavigation<MailList, MailListViewModel>();
        }

这里面有好多类其实之前就加了,但是考虑到思考的逻辑顺序,之前没加.现在挨个加上.
MailList.xaml 和 MailListViewModel.cs
这个界面是 点击Mail按钮时,ContentRegion 要导航到的View

<UserControl x:Class="PrsimOutlook.Modules.Mail.Views.MailList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <TextBlock Text="{Binding Title}"></TextBlock>
    </Grid>
</UserControl>

namespace PrsimOutlook.Modules.Mail.ViewModels
{
    public class MailListViewModel : BindableBase
    {
        public MailListViewModel()
        {
        }
        private string _title = "Default";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }
    }
}

IOutlookBarGroup 接口,给继承 OutlookBarGroup 的窗体 使用

namespace PrismOutlook.Core
{
    public interface IOutlookBarGroup
    {
        string DefaultNavigationPath { get; }
    }
}

这个接口用来记录 IOutlookBarGroup中各个内容中的默认导航路径

在 MailGroup.xaml.cs 中

 public partial class MailGroup : OutlookBarGroup,IOutlookBarGroup
    {
        public MailGroup()
        {
            InitializeComponent();
        }

        public string DefaultNavigationPath => "MailList";
                                                
    }

注意 这个 DefaultNavigationPath 写的路径要和Module 使用 RegisterForNavigation 注册的View类名一致,否则找不到.这是Prsim在后台提供的按名称匹配的一种模式

在 ContactsGroup.xaml.cs 中

namespace PrsimOutlook.Modules.Contacts.Menus
{
    /// <summary>
    /// ContactsGroup.xaml 的交互逻辑
    /// </summary>
    public partial class ContactsGroup : OutlookBarGroup,IOutlookBarGroup
    {
        public ContactsGroup()
        {
            InitializeComponent();
        }       
        public string DefaultNavigationPath => "ViewA";
    }
}

让我们来整理一下这个点击 Mali,Contacts按钮,右边ContentControl切换 View 的逻辑

  1. 点击按钮,进入 XamOutlookBar_SelectedGroupChanged 事件
  2. 将 sender as成 IOutlookBarGroup接口
  3. 调用 _regionManager.RequestNavigate(RegionNames.ContentRegion, group.DefaultNavigationPath);进行导航.
  4. 这个导航调用的前提是注册了导航,注册导航的View 类名要与字符串
    group.DefaultNavigationPath一样,本例之前已经注册了 Navigation
containerRegistry.RegisterForNavigation<MailList, MailListViewModel>();
containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
  1. 所以点击按钮时,可以匹配到对应的视图,导航成功.

由于后面还要对 Mail中的 Inbox,Deleted,Send按钮进行导航,所以觉得这种方式不太好,下面要把这个导航改成 Prsim 中的 CompositeCommand , CompositeCommand 可以执行一个命令的同时,执行这个命令所绑定的所有子命令.

首先创建 IApplicationCommands接口,ApplicationCommands 继承 IApplicationCommands

namespace PrismOutlook.Core
{
    public interface IApplicationCommands
    {
        CompositeCommand NavigateCommand { get; }
    }

    public class ApplicationCommands : IApplicationCommands
    {
        public CompositeCommand NavigateCommand { get; } = new CompositeCommand();
    }
}

然后需要在 App.xaml.cs里通过单例的方式注册一下.

 protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>();
        }

然后给 CompositeCommand 注册一下子命令
首先在MainWindowViewModel.cs中 先创建这个子命令

		private DelegateCommand<string> _navigateCommand;
        public DelegateCommand<string> NavigateCommand =>
            _navigateCommand ?? (_navigateCommand = new DelegateCommand<string>(ExecuteNavigateCommand));

		void ExecuteNavigateCommand(string navigationPath)
        {
            if (string.IsNullOrEmpty(navigationPath))
                throw new ArgumentNullException();

            _regionManager.RequestNavigate(RegionNames.ContentRegion, navigationPath);
        }

然后注册一下子命令

public MainWindowViewModel(IRegionManager regionManager, IApplicationCommands applicationCommands)
        {
            _regionManager = regionManager;
            applicationCommands.NavigateCommand.RegisterCommand(NavigateCommand);
        }

再次感叹一下 容器的好处, Prsim用起来真的很舒服.

在MainWindow.xaml.cs

public partial class MainWindow : XamRibbonWindow
    {
        private readonly IApplicationCommands _applicationCommands;
        public MainWindow(IApplicationCommands applicationCommands)
        {
            InitializeComponent();
            Infragistics.Themes.ThemeManager.ApplicationTheme = new Office2013Theme();
            this._applicationCommands = applicationCommands;
        }
        private void XamOutlookBar_SelectedGroupChanged(object sender, RoutedEventArgs e)
        {
            var group = ((XamOutlookBar)sender).SelectedGroup as IOutlookBarGroup;
            if (group != null)
            {
                          _applicationCommands.NavigateCommand.Execute(group.DefaultNavigationPath);
            }
        }
    }

将原来的 XamOutlookBar_SelectedGroupChanged函数写法换成这样的写法,完成了同样的功能.

现在是把点击 MailGroup 和Contacts的导航做好了,还有 MailGroup中的 Inbox,Send,Deleted的导航.

首先需要在 MainGroup.xaml ig:XamDataTree 节点中

<i:Interaction.Triggers>
                <i:EventTrigger EventName="ActiveNodeChanged">
                    <prism:InvokeCommandAction Command="{Binding SelectedCommand}" TriggerParameterPath="NewActiveTreeNode.Data" />
                </i:EventTrigger>
            </i:Interaction.Triggers>

然后在 MainGroupViewModel.cs 写一下 SelectedCommand,这个Command其实是要做导航功能的,所以直接用这个Command调用之前写的 CompositeCommand,代码如下

 		private DelegateCommand<NavigationItem> _selectedCommand;
        private readonly IApplicationCommands _applicationCommands;

        public DelegateCommand<NavigationItem> SelectedCommand =>
            _selectedCommand ?? (_selectedCommand = new DelegateCommand<NavigationItem>(ExecuteSelectCommand));

        
        public MainGroupViewModel(IApplicationCommands applicationCommands)
        {
            GenerateMenu();
            this._applicationCommands = applicationCommands;
            
        }
        void ExecuteSelectCommand(NavigationItem parameter)
        {
            //"MailList?Folder=Deleted"
            //"MailList?id=Inbox"
            if (parameter != null)
                _applicationCommands.NavigateCommand.Execute(parameter.NavigationPath);
        }
		void GenerateMenu()
        {
            Items = new ObservableCollection<NavigationItem>();
            var root = new NavigationItem() { Caption = "Personal Folder", NavigationPath = "MailList?id=Default" };
            root.Items.Add(new NavigationItem() { Caption = "Inbox", NavigationPath = "MailList?id=Inbox" });
            root.Items.Add(new NavigationItem() { Caption = "Deleted", NavigationPath = "MailList?id=Deleted" });
            root.Items.Add(new NavigationItem() { Caption = "Sent", NavigationPath = "MailList?id=Sent" });
            Items.Add(root);
        }

其中还要注意一下,我们把 GenerateMenu 函数中的 NavigationPath 分别加上了对应的参数.现在的情况下还都是显示Default.想要实现一个点击 Inbox, MailList 中的 TextBlock 显示 Inbox.点击Sent,显示Sent.

这个就需要在 MailList.xaml.cs 中继承一下 INavigationAware,实现里面的接口

		public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }

        public void OnNavigatedTo(NavigationContext navigationContext)
        {
			Title = navigationContext.Parameters.GetValue<string>("id");
        }

OnNavigatedTo 函数的介绍如下

//
        // 摘要:
        //     Called when the implementer has been navigated to.
        //     执行器被导航到时调用
        // 参数:
        //   navigationContext:
        //     The navigation context.
        	   导航上下文
        void OnNavigatedTo(NavigationContext navigationContext);

然后在点击 Mail 和 Contacts时发现 从 Contacts点回到Mail 时,没有把上一次 Contacts选中的View记录住.在MailGroup.xaml.cs里改一下这段代码

		public string DefaultNavigationPath 
        {
            get
            {
                var item = _dataTree.SelectionSettings.SelectedNodes[0] as XamDataTreeNode;
                if(item != null)
                {
                    return ((NavigationItem)item.Data).NavigationPath;
                }

                return "MailList";
            }
        }

这样就实现了 文章最开始的图片的效果.
后面老哥稍微重构了一下.
在Core中先创建了一个 ViewModelBase.cs,里面继承了BindableBase和IConfirmNavigationRequest
把方法设为虚方法

	public class ViewModelBase : BindableBase,IConfirmNavigationRequest
    {
        public virtual void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            continuationCallback(true);
        }

        public virtual bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public virtual void OnNavigatedFrom(NavigationContext navigationContext)
        {
        }

        public virtual void OnNavigatedTo(NavigationContext navigationContext)
        {

        }
    }

然后把各个ViewModel都改为继承 ViewModelBase

后面还有一些内容,但是功能还没实现,只是搭了基础设施,我会放到下篇去写.这节视频这天是这位老哥的结婚18周年纪念日,迫不及待得去接老婆下班了.

总结

这节主要就是实现了点击Mail和点击Contacts,右边的ContentRegion进行视图的切换.
首先先实现了Mail和Contacts的切换,分别显示 MailList 和 ViewA的视图.
在MailGroup中创建了 Inbox,Sent,Deleted子节点
然后实现了点击 Inbox,Sent,Deleted时,右边 MailList 中的TextBlock显示对应的内容.
在实现中使用了CompositeCommand,RequestNavigate,使用了 IOutlookBarGroup 接口解耦.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值