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

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

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

以往文章链接

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

相关链接

视频二 Changing Ribbon Tabs 学习记录

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

这节视频主要还是在搭建框架,做到了不同的ViewModel之间,共享属性.干货很多
在这里插入图片描述
首先先把上一节没有写的内容整理一下,先把MailModule.cs的 RibbonRegion的注册删掉

public void OnInitialized(IContainerProvider containerProvider)
{
    //remove
    //_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewA));
	//remove
    //_regionManager.RegisterViewWithRegion(RegionNames.RibbonRegion, typeof(HomeTab));

    _regionManager.RegisterViewWithRegion(RegionNames.OutlookGroupRegion, typeof(MailGroup)); 
}

现在程序启动的话,上方RibbonRegion是空的,没有内容.

现在的情况是
当点击MailGroup中的Mail时,ContentRegion会加载 MailList.xaml
点击MailGroup中的Contacts时,ContentRegion会加载 ViewA.xaml

我们想做的是当加载MailList.xmal时,RibbonRegion加载MailGroup的HomeTab.xaml,
加载ViewA.xaml时,RibbonRegion加载Contacts的HomeTab.xaml

这位老哥想通过 Attribute来实现这个功能.
现在在 Core下项目里建立了这个DependentViewAtrribute 类,继承Attribute

namespace PrismOutlook.Core
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class DependentViewAtrribute :Attribute
    {
        public string Region { get; set; }
        public Type Type { get; set; }

        public DependentViewAtrribute(string region, Type type)
        {
            if (region is null)
            {
                throw new ArgumentNullException(nameof(region));
            }

            if (type == null)
                throw new ArgumentNullException(nameof(type));

            Type = type;
            Region = region;
        }
    }
}

声明了指定的Region和Type,现在要到指定的MailList和ViewA使用去使用.
在MailList 中指定到 Mail中的 HomeTab.xaml

using PrsimOutlook.Modules.Mail.Menus;
namespace PrsimOutlook.Modules.Mail.Views
{
    /// <summary>
    /// Interaction logic for MailList
    /// </summary>
    /// 
    [DependentViewAtrribute(RegionNames.RibbonRegion, typeof(HomeTab))]
    public partial class MailList : UserControl,ISupportDataContext
    {
      
        public MailList()
        {
            InitializeComponent();
        }
    }
}

在ViewA中指定到 Contacts中的 HomeTab.xaml

using PrsimOutlook.Modules.Contacts.Menus;
namespace PrsimOutlook.Modules.Contacts.Views
{
    /// <summary>
    /// Interaction logic for ViewA.xaml
    /// </summary>
    /// 
    [DependentViewAtrribute(RegionNames.RibbonRegion, typeof(HomeTab))]
    public partial class ViewA : UserControl
    {
        public ViewA()
        {
            InitializeComponent();
        }
    }
}

这个对应关系已经建立好了,那要怎么实现功能呢.
首先创建一个 DependentViewRegionBehavior.cs,这个类要继承 RegionBehavior类,这个是Prsim提供的一个抽象类,需要override OnAttach方法,并在这个方法中添加一下 ActiveViews_CollectionChanged事件,代码如下.

namespace PrismOutlook.Core.Regions
{
    public class DependentViewRegionBehavior : RegionBehavior
    {
        public const string BehaviorKey = "DependentViewRegionBehavior";
        public DependentViewRegionBehavior()
        {        
        }
        protected override void OnAttach()
        {
            Region.ActiveViews.CollectionChanged += ActiveViews_CollectionChanged;
        }

        private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
			}
        }
    }
}

创建好类之后,需要在App.xaml.cs中添加一下.

 protected override void ConfigureDefaultRegionBehaviors(IRegionBehaviorFactory regionBehaviors)
 {
     base.ConfigureDefaultRegionBehaviors(regionBehaviors);
     regionBehaviors.AddIfMissing(DependentViewRegionBehavior.BehaviorKey,typeof(DependentViewRegionBehavior));
 }

回到 DependentViewRegionBehavior.cs类中

 //
 // 摘要:
 //     Gets a readonly view of the collection of all the active views in the region.
 //		获取区域内所有活动视图集合的只读视图。
 //
 // 值:
 //     An Prism.Regions.IViewsCollection of all the active views.
 //     一个包含所有活动视图的 
 IViewsCollection ActiveViews { get; }

可以看到,
当点击MailGroup按钮时,MailList这个View会添加入 Region.ActiveViews这个集合中.所以e.Action就等于 NotifyCollectionChangedAction.Add
当从MailGroup按钮切换到Contacts界面,e.Action会等于 NotifyCollectionChangedAction.Remove,然后e.Action会再等于NotifyCollectionChangedAction.Add.
所以想要实现我们的功能,就在 ActiveViews_CollectionChanged 这个函数里面就可以做到了.

if (e.Action == NotifyCollectionChangedAction.Add)
{
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
}

在写之前,我们先理一下逻辑.

  1. 当新View被添加时,我们是知道被添加的是哪个View的,获取到View之后,我们要获取他的Type去得到这个Type的的Attribute.
  2. 得到Attribute之后,我们就知道了这个View上面Attribute的Region,对应RibbonRegion要加载的View的Type
  3. 得到了Type之后,我们要在对应的Region上添加View

首先在DependentViewRegionBehavior.cs中先写一个函数,用来获取给定类型(type)上的指定自定义属性(T)的集合

 private static IEnumerable<T> GetCustomsAttributes<T>(Type type)
 {
     return type.GetCustomAttributes(typeof(T), true).OfType<T>();
 }

当然这个函数不写也可以,只不过就要这么写

var atts = newView.GetType().GetCustomAttributes(typeof(DependentViewAtrribute), true).OfType<DependentViewAtrribute>();

再创建一个类,代表得到的Attribute所对应的内容.

namespace PrismOutlook.Core.Regions
{
    public class DependentViewInfo
    {
        public object View { get; set; }

        public string Region{ get; set; }
    }
}

再在DependentViewRegionBehavior.cs中写一个函数,用来接收Attribute,返回DependentViewInfo.
Prsim在app.xaml.cs外使用容器获取实例,需要使用 IContainerExtension

private readonly IContainerExtension _container;
public DependentViewRegionBehavior(IContainerExtension container)
{
    this._container = container;
}
DependentViewInfo CreateDependentViewInfo(DependentViewAtrribute atrribute)
{
    var info = new DependentViewInfo();
    info.Region = atrribute.Region;
    info.View = _container.Resolve(atrribute.Type);
    return info;

}

下面就可以写相关的内容了.

if (e.Action == NotifyCollectionChangedAction.Add)
{

    foreach (var newView in e.NewItems)
    {
        var atts = GetCustomsAttributes<DependentViewAtrribute>(newView.GetType());
        foreach (var att in atts)
        {
            var info = CreateDependentViewInfo(att);
            Region.RegionManager.Regions[info.Region].Add(info.View);
        }
    }
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{

}

可以看到,在点击Mail和Contacts的过程中,RibbonRegion已经把Mail对应的View和Contacts对应的View显示出来了.
在这里插入图片描述
现在还没写删除,所以这个会一直增长,现在解决这个问题.
创建一个Dictionary Cache

Dictionary<object,List<DependentViewInfo>> _dependentViewCache = new Dictionary<object,List<DependentViewInfo>>();

再改造一下这个事件

private void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {

        foreach (var newView in e.NewItems)
        {
            var dependentViews = new List<DependentViewInfo>();


            //check if view alread has dependents
            //if fit does use those
            //if not,create them
            if (_dependentViewCache.ContainsKey(newView))
            {
                //reuse
                dependentViews = _dependentViewCache[newView];
            }
            else
            {
                // get atrribute
                var atts = GetCustomsAttributes<DependentViewAtrribute>(newView.GetType());

                //var atts = newView.GetType().GetCustomAttributes(typeof(DependentViewAtrribute), true).OfType<DependentViewAtrribute>();
                foreach (var att in atts)
                {
                    var info = CreateDependentViewInfo(att);

                    dependentViews.Add(info);
                }

                _dependentViewCache.Add(newView, dependentViews);
            }

            dependentViews.ForEach(item => Region.RegionManager.Regions[item.Region].Add(item.View));
        }

    }
    else if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach (var oldview in e.OldItems)
        {
            var dependentViews = _dependentViewCache[oldview];
            dependentViews.ForEach(item => Region.RegionManager.Regions[item.Region].Remove(item.View));
            _dependentViewCache.Remove(oldview);
        }
    }
}

这样就完成了增加和删除,不会RibbonRegion上HomeTab就不会无限制的往外面增了.
在这里插入图片描述
下面是一个比较精彩的地方,我们存在一个这样的需求,在Inbox界面.我们可能有回复邮件,新建邮件的需求,我们希望RibbonRegion中的HomeTab可以共享到Inbox界面中的命令,属性,可以在HomeTab.xaml中 Binding MailListViewModel的属性.下面写这个实现.

先创建ISupportDataContext接口

namespace PrismOutlook.Core
{
    public interface ISupportDataContext
    {
         object DataContext { get; set; }
    }
}

分别让 MailList.xaml.cs 和 MailModule中的HomeTab.xaml.cs继承 ISupportDataContext接口

MailList.xaml.cs

namespace PrsimOutlook.Modules.Mail.Views
{
    /// <summary>
    /// Interaction logic for MailList
    /// </summary>
    /// 
    [DependentViewAtrribute(RegionNames.RibbonRegion, typeof(HomeTab))]
    public partial class MailList : UserControl,ISupportDataContext
    {      
        public MailList()
        {
            InitializeComponent();           
        }
    }
}

HomeTab.xaml.cs

namespace PrsimOutlook.Modules.Mail.Menus
{
    /// <summary>
    /// HomeTab.xaml 的交互逻辑
    /// </summary>
    public partial class HomeTab : RibbonTabItem,ISupportDataContext
    {
        public HomeTab()
        {
            InitializeComponent();           
            SetResourceReference(StyleProperty, typeof(RibbonTabItem));
        }       
    }
}

之后在 DependentViewRegionBehavior.cs 类中ActiveViews_CollectionChanged事件中加上这一段

foreach (var att in atts)
{
    var info = CreateDependentViewInfo(att);
    //在之前的中间插上这一段
    if (info.View is ISupportDataContext infoDC && newView is ISupportDataContext viewDC)
    {
        infoDC.DataContext = viewDC.DataContext;
    }
    //end
    dependentViews.Add(info);
}

这段的意思是这样:

  • newView是MailList
  • info.View就是获得的HomeTab
  • infoDC.DataContext = viewDC.DataContext;是将MailList和HomeTab转成 ISupportDataContext ,将MailList的 DataContext 赋值给DataContext
  • MailList是UserControl,MailList的DataContext是和MailListViewModel绑定上的
  • HomeTab也是UserControl,所以MailListViewModel的属性就和HomeTab共享了.

下面试验一下这个功能
把MailList.xaml的TextBlock改成Button,并绑定一个Command

在MailList.xaml中

<Button Content="{Binding Title}" Command="{Binding TestCommand}"></Button>

在 MailListViewModel.cs 中

 private DelegateCommand _testCommand;
 public DelegateCommand TestCommand =>
     _testCommand ?? (_testCommand = new DelegateCommand(ExecuteCommandName));

 void ExecuteCommandName()
 {
     MessageBox.Show("test");
 }

在 HomeTab.xaml 中

<ig:RibbonGroup Caption="New">
    <ig:ButtonTool Caption="New Email"  Command="{Binding TestCommand}"
                  ig:RibbonGroup.MaximumSize="ImageAndTextLarge"/>
</ig:RibbonGroup>

运行一下,两个地方的按钮都可以弹出 test 提示窗口
在这里插入图片描述
把这个 TestCommand 取消掉,把MailList.xaml修改一下UI界面

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ig:XamDataGrid></ig:XamDataGrid>
    <Grid Grid.Column="1" MinWidth="250" Background="LightBlue"></Grid>
</Grid>

界面显示如下
在这里插入图片描述
这节内容到这里就结束了,下一节会建立服务

总结

这节主要还是在搭建框架,使用了DependentViewRegionBehavior继承了 RegionBehavior和自定义属性进行RibbonRegion 导航.同时创建了ISupportDataContext接口,让MailList和HomeTab同时继承ISupportDataContext这个接口,在DependentViewRegionBehavior中将MailList和HomeTab转成 ISupportDataContext ,将MailList的 DataContext 赋值给DataContext,这样就做到了共享.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值