AvalonDock中使用Prism进行导航

最近公司的项目中需要对AvalonDock的窗体使用Prism进行导航,于是就研究了一下。老实说感觉Avalon不好用,“Avalon之难用,简直玷污了吾王的理想乡”是我第一次使用Avalon之后的想法。但是WPF下支持MVVM的Dock也没发现别的,至少大家都在用这个。没办法~

本次的经验是从StakOverFlowPrism的官方介绍学习来的。英文好的朋友可以直接阅读原文。

问题介绍

要实现的大概就是一个VS一样的效果。具备以下几个功能:

  1. Dock功能,能拖拽分屏
  2. 从一个树结构中点击节点,在主窗口中显示节点内容

解决思路

问题 解决方案
1. Dock功能,能拖拽分屏 AvalonDock
2. 从一个树结构中点击节点,在主窗口中显示节点内容 Prism的Navigation功能

PS: 如果没有AvalonDock和Prism基础的朋友先去补一下基础知识。

AvalonDock中的主承载界面(也就是VS中代码编辑部分),可以看到是非常类似于TabControl的。那么Prism如何多TabControl进行的导航呢?

  1. Module中声明需要导航的View
  2. Module中的Module类里注册View
public class ModuleTestModule : IModule
{
      IRegionManager _regionManager;
      IUnityContainer _container;

      public ModuleTestModule(RegionManager regionManager, IUnityContainer container)
      {
            _regionManager = regionManager;
            _container = container;
      }

      public void Initalize()
      {
            //TestView就是在上一步定义的View的名字
            //这里进行View的注册
            _container.RegisterTypeForNavigation<TestView>();
      }
}
  1. 在Shell中定义Region
<Tabcontrol prism:RegionManager.RegionName="TestRegion">
</Tabcontrol>
  1. 在Bootstrapper中添加Module
protected override void ConfigureModuleCatalog()
{
      var moduleCatalog = (ModuleCatalog)ModuleCatalog;
      moduleCatalog.AddModule(typeof(SolutionExplorer.SolutionExplorerModule));
}
  1. 适当的时候进行导航
//第一个参数是要导航的Region,第二个参数是要导航的View
regionManager.RequestNavigation("TestRegion","TestView");

以上就是我们基本的对TabControl的导航。

照猫画虎,我们尝试在Avalon中这么使用一下。
下面是AvalonDock声明主承载部分的代码。

        <avalonDock:DockingManager >
            <avalonDock:LayoutRoot>
                <avalonDock:LayoutPanel Orientation="Horizontal">
                    <avalonDock:LayoutDocumentPaneGroup DockWidth="100" Orientation="Vertical">
                        <avalonDock:LayoutDocumentPane>

                        </avalonDock:LayoutDocumentPane>
                    </avalonDock:LayoutDocumentPaneGroup>
                </avalonDock:LayoutPanel>
            </avalonDock:LayoutRoot>
        </avalonDock:DockingManager>

StakeOverFlow上说,LayoutPanelLayoutDocumentPaneGroup等是不能使用prism:RegionManager.RegionName="xxx"的。不过说这话的时候还是好几年前,环境为AvalonDock2以及Prism4。目前的Prism已经到了6,是否支持不知道,没试过,有兴趣的朋友可以试一下。

所以,我们给Avalon加上Region

        <avalonDock:DockingManager prism:RegionManager.RegionName="TestRegion">
            <avalonDock:LayoutRoot>
                <avalonDock:LayoutPanel Orientation="Horizontal">
                    <avalonDock:LayoutDocumentPaneGroup DockWidth="100" Orientation="Vertical">
                        <avalonDock:LayoutDocumentPane>

                        </avalonDock:LayoutDocumentPane>
                    </avalonDock:LayoutDocumentPaneGroup>
                </avalonDock:LayoutPanel>
            </avalonDock:LayoutRoot>
        </avalonDock:DockingManager>

看起来和TabControl添加没什么区别。然后我们运行一下。嗯,不出意料的报错了。
//回头补张图

提示我们少了RegionAdapter。嗯?什么玩意,没见过。
字面意思,就是一个Region的适配器。干什么的呢?就是将Region导航时,应该执行什么动作,封装一下。将Region和目标类型进行一个适配。以下是Prism官方介绍原话:

Region adapters control how items placed in a region interact with the host control.
适配器通过和载体控件交互,控制具体项在Region中的位置。

嗯,少了个适配器我们就去建一个好了。有经验一点的朋友可能已经开始写类了。

public class AvalonDockingRegionAdapter : IRegionAdapter
{  
    //IRegionAdapter的方法
    public IRegion Initialize(object regionTarget, string regionName)
    {
        ...
    }
}

然而这并不是正确的打开方式。再上一次官方原话:

To create a region adapter, you derive your class from RegionAdapterBase and implement the CreateRegion and Adapt methods. Optionally, override the AttachBehaviors method to attach special logic to customize the region behavior. If you want to interact with the control that hosts the region, you should also implement IHostAwareRegionBehavior.
要创建一个Region Adapter,你需要继承RegionAdapterBase并且实现CreateReionAdapt方法。如果有需要,重写AttachBehaviors方法来添加特殊的逻辑到自定义的Region Behavior。如果你想要和承载Region的控件交互,你应该实现IHostAwareRegionBehavior

灰常好,我们按着做。

    public class AvalonDockingRegionAdapter : RegionAdapterBase<DockingManager>
    {
       #region Constructor

        public AvalonDockRegionAdapter(IRegionBehaviorFactory factory)
            : base(factory)
        {
        }

        #endregion  //Constructor


        #region Overrides

        protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }

        protected override void Adapt(IRegion region, DockingManager regionTarget)
        {
            region.Views.CollectionChanged += delegate(
                Object sender, NotifyCollectionChangedEventArgs e)
                {
                    this.OnViewsCollectionChanged(sender, e, region, regionTarget);
                };

            regionTarget.DocumentClosed += delegate(
                            Object sender, DocumentClosedEventArgs e)
            {
                this.OnDocumentClosedEventArgs(sender, e, region);
            };
        }

        #endregion  //Overrides


        #region Event Handlers

        /// <summary>
        /// Handles the NotifyCollectionChangedEventArgs event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event.</param>
        /// <param name="region">The region.</param>
        /// <param name="regionTarget">The region target.</param>
        void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DockingManager regionTarget)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (FrameworkElement item in e.NewItems)
                {
                    UIElement view = item as UIElement;

                    if (view != null)
                    {
                        //Create a new layout document to be included in the LayoutDocuemntPane (defined in xaml)
                        LayoutDocument newLayoutDocument = new LayoutDocument();
                        //Set the content of the LayoutDocument
                        newLayoutDocument.Content = item;

                        ViewModelBase_2 viewModel = (ViewModelBase_2)item.DataContext;

                        if (viewModel != null)
                        {
                            //All my viewmodels have properties DisplayName and IconKey
                            newLayoutDocument.Title = viewModel.DisplayName;
                            //GetImageUri is custom made method which gets the icon for the LayoutDocument
                            newLayoutDocument.IconSource = this.GetImageUri(viewModel.IconKey);
                        }

                        //Store all LayoutDocuments already pertaining to the LayoutDocumentPane (defined in xaml)
                        List<LayoutDocument> oldLayoutDocuments = new List<LayoutDocument>();
                        //Get the current ILayoutDocumentPane ... Depending on the arrangement of the views this can be either 
                        //a simple LayoutDocumentPane or a LayoutDocumentPaneGroup
                        ILayoutDocumentPane currentILayoutDocumentPane = (ILayoutDocumentPane)regionTarget.Layout.RootPanel.Children[0];

                        if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPaneGroup))
                        {
                            //If the current ILayoutDocumentPane turns out to be a group
                            //Get the children (LayoutDocuments) of the first pane
                            LayoutDocumentPane oldLayoutDocumentPane = (LayoutDocumentPane)currentILayoutDocumentPane.Children.ToList()[0];
                            foreach (LayoutDocument child in oldLayoutDocumentPane.Children)
                            {
                                oldLayoutDocuments.Insert(0, child);
                            }
                        }
                        else if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPane))
                        {
                            //If the current ILayoutDocumentPane turns out to be a simple pane
                            //Get the children (LayoutDocuments) of the single existing pane.
                            foreach (LayoutDocument child in currentILayoutDocumentPane.Children)
                            {
                                oldLayoutDocuments.Insert(0, child);
                            }
                        }

                        //Create a new LayoutDocumentPane and inserts your new LayoutDocument
                        LayoutDocumentPane newLayoutDocumentPane = new LayoutDocumentPane();
                        newLayoutDocumentPane.InsertChildAt(0, newLayoutDocument);

                        //Append to the new LayoutDocumentPane the old LayoutDocuments
                        foreach (LayoutDocument doc in oldLayoutDocuments)
                        {
                            newLayoutDocumentPane.InsertChildAt(0, doc);
                        }

                        //Traverse the visual tree of the xaml and replace the LayoutDocumentPane (or LayoutDocumentPaneGroup) in xaml
                        //with your new LayoutDocumentPane (or LayoutDocumentPaneGroup)
                        if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPane))
                            regionTarget.Layout.RootPanel.ReplaceChildAt(0, newLayoutDocumentPane);
                        else if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPaneGroup))
                        {
                            currentILayoutDocumentPane.ReplaceChild(currentILayoutDocumentPane.Children.ToList()[0], newLayoutDocumentPane);
                            regionTarget.Layout.RootPanel.ReplaceChildAt(0, currentILayoutDocumentPane);
                        }
                        newLayoutDocument.IsActive = true;
                    }
                }
            }
        }

        /// <summary>
        /// Handles the DocumentClosedEventArgs event raised by the DockingNanager when
        /// one of the LayoutContent it hosts is closed.
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The event.</param>
        /// <param name="region">The region.</param>
        void OnDocumentClosedEventArgs(object sender, DocumentClosedEventArgs e, IRegion region)
        {
            region.Remove(e.Document.Content);
        }

        #endregion   
    }

然后记得在Bootstrapper里面添加

// Bootstrapper.cs
protected virtual RegionAdapterMappings ConfigureRegionAdapterMappings()
{
    RegionAdapterMappings regionAdapterMappings = ServiceLocator.Current.GetInstance<RegionAdapterMappings>();
    if (regionAdapterMappings != null)
    {
        regionAdapterMappings.RegisterMapping(typeof(DockingManager), ServiceLocator.Current.GetInstance<AvalonDockRegionManager>());
    }
    return regionAdapterMappings;
}

好,完事,收工。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 摄于16年3月20日晚上七点到哈尔滨太平国际机场 去年的今天回学校考专业八级,和娜娜两人一起从海口到哈尔滨,这是我...
    Lunicorn露阅读 229评论 0 0
  • Android网络与数据存储 第一章学习 一个启动引导页的制作#### 概要: 这次制作App的引导页,主要用到2...
    爱因斯坦福阅读 2,686评论 0 9
  • 今天是儿子学跆拳道最后一天,周日汇报演出,儿子昨晚问我,去不去,由于儿子不听话了,我说了不去,有点后悔...
    子瀚璐菡妈妈阅读 215评论 0 0
  • 简述 在ARPG或写实或奇幻的大潮中,大多追求炫丽的特效和磅礴的视觉冲击,而《蜀山战纪》(以下简称《蜀山》)却一反...
    王世震阅读 478评论 0 0