AvalonDock中使用Prism進行導航

最近公司的項目中需要對AvalonDock的窗體使用Prism進行導航酗失,于是就研究了一下。老實說感覺Avalon不好用灭贷,“Avalon之難用,簡直玷污了吾王的理想鄉(xiāng)”是我第一次使用Avalon之后的想法。但是WPF下支持MVVM的Dock也沒發(fā)現(xiàn)別的,至少大家都在用這個。沒辦法~

本次的經(jīng)驗是從StakOverFlowPrism的官方介紹學習來的。英文好的朋友可以直接閱讀原文粉渠。

問題介紹

要實現(xiàn)的大概就是一個VS一樣的效果。具備以下幾個功能:

  1. Dock功能圾另,能拖拽分屏
  2. 從一個樹結構中點擊節(jié)點渣叛,在主窗口中顯示節(jié)點內(nèi)容

解決思路

問題 解決方案
1. Dock功能,能拖拽分屏 AvalonDock
2. 從一個樹結構中點擊節(jié)點盯捌,在主窗口中顯示節(jié)點內(nèi)容 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. 適當?shù)臅r候進行導航
//第一個參數(shù)是要導航的Region,第二個參數(shù)是要導航的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"的。不過說這話的時候還是好幾年前识椰,環(huán)境為AvalonDock2以及Prism4绝葡。目前的Prism已經(jīng)到了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添加沒什么區(qū)別愉阎。然后我們運行一下。嗯力奋,不出意料的報錯了榜旦。
//回頭補張圖

提示我們少了RegionAdapter。嗯景殷?什么玩意溅呢,沒見過锉走。
字面意思,就是一個Region的適配器藕届。干什么的呢?就是將Region導航時亭饵,應該執(zhí)行什么動作休偶,封裝一下。將Region和目標類型進行一個適配辜羊。以下是Prism官方介紹原話:

Region adapters control how items placed in a region interact with the host control.
適配器通過和載體控件交互踏兜,控制具體項在Region中的位置。

嗯八秃,少了個適配器我們就去建一個好了碱妆。有經(jīng)驗一點的朋友可能已經(jīng)開始寫類了。

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.
要創(chuàng)建一個Region Adapter疹尾,你需要繼承RegionAdapterBase并且實現(xiàn)CreateReionAdapt方法。如果有需要骤肛,重寫AttachBehaviors方法來添加特殊的邏輯到自定義的Region Behavior纳本。如果你想要和承載Region的控件交互,你應該實現(xiàn)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;
}

好淑玫,完事巾腕,收工。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末絮蒿,一起剝皮案震驚了整個濱河市尊搬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌土涝,老刑警劉巖毁嗦,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異回铛,居然都是意外死亡狗准,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門茵肃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腔长,“玉大人,你說我怎么就攤上這事验残±谈剑” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鸟召。 經(jīng)常有香客問我胆绊,道長,這世上最難降的妖魔是什么欧募? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任压状,我火速辦了婚禮,結果婚禮上跟继,老公的妹妹穿的比我還像新娘种冬。我一直安慰自己,他們只是感情好舔糖,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布娱两。 她就那樣靜靜地躺著,像睡著了一般金吗。 火紅的嫁衣襯著肌膚如雪十兢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天摇庙,我揣著相機與錄音纪挎,去河邊找鬼。 笑死跟匆,一個胖子當著我的面吹牛异袄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玛臂,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼烤蜕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迹冤?” 一聲冷哼從身側響起讽营,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泡徙,沒想到半個月后橱鹏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡堪藐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年莉兰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礁竞。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡糖荒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出模捂,到底是詐尸還是另有隱情捶朵,我是刑警寧澤蜘矢,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站综看,受9級特大地震影響品腹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜红碑,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一舞吭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧句喷,春花似錦、人聲如沸兔毙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澎剥。三九已至锡溯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哑姚,已是汗流浹背祭饭。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叙量,地道東北人倡蝙。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像绞佩,于是被迫代替她去往敵國和親寺鸥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理品山,服務發(fā)現(xiàn)胆建,斷路器,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • 攝于16年3月20日晚上七點到哈爾濱太平國際機場 去年的今天回學兄饨唬考專業(yè)八級笆载,和娜娜兩人一起從海口到哈爾濱涯呻,這是我...
    Lunicorn露閱讀 234評論 0 0
  • Android網(wǎng)絡與數(shù)據(jù)存儲 第一章學習 一個啟動引導頁的制作#### 概要: 這次制作App的引導頁凉驻,主要用到2...
    愛因斯坦福閱讀 2,688評論 0 9
  • 今天是兒子學跆拳道最后一天,周日匯報演出复罐,兒子昨晚問我沿侈,去不去,由于兒子不聽話了市栗,我說了不去缀拭,有點后悔...
    子瀚璐菡媽媽閱讀 215評論 0 0
  • 簡述 在ARPG或?qū)憣嵒蚱婊玫拇蟪敝锌榷蹋蠖嘧非箪披惖奶匦Ш桶蹴绲囊曈X沖擊,而《蜀山戰(zhàn)紀》(以下簡稱《蜀山》)卻一反...
    王世震閱讀 483評論 0 0