最近公司的項目中需要對AvalonDock的窗體使用Prism進行導航酗失,于是就研究了一下。老實說感覺Avalon不好用灭贷,“Avalon之難用,簡直玷污了吾王的理想鄉(xiāng)”是我第一次使用Avalon之后的想法。但是WPF下支持MVVM的Dock也沒發(fā)現(xiàn)別的,至少大家都在用這個。沒辦法~
本次的經(jīng)驗是從StakOverFlow和Prism的官方介紹學習來的。英文好的朋友可以直接閱讀原文粉渠。
問題介紹
要實現(xiàn)的大概就是一個VS一樣的效果。具備以下幾個功能:
- Dock功能圾另,能拖拽分屏
- 從一個樹結構中點擊節(jié)點渣叛,在主窗口中顯示節(jié)點內(nèi)容
解決思路
問題 | 解決方案 |
---|---|
1. Dock功能,能拖拽分屏 | AvalonDock |
2. 從一個樹結構中點擊節(jié)點盯捌,在主窗口中顯示節(jié)點內(nèi)容 | Prism的Navigation功能 |
PS: 如果沒有AvalonDock和Prism基礎的朋友先去補一下基礎知識淳衙。
AvalonDock中的主承載界面(也就是VS中代碼編輯部分),可以看到是非常類似于TabControl的饺著。那么Prism如何多TabControl進行的導航呢箫攀?
- Module中聲明需要導航的View
- 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>();
}
}
- 在Shell中定義Region
<Tabcontrol prism:RegionManager.RegionName="TestRegion">
</Tabcontrol>
- 在Bootstrapper中添加Module
protected override void ConfigureModuleCatalog()
{
var moduleCatalog = (ModuleCatalog)ModuleCatalog;
moduleCatalog.AddModule(typeof(SolutionExplorer.SolutionExplorerModule));
}
- 適當?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上說梢睛,LayoutPanel 和 LayoutDocumentPaneGroup等是不能使用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)CreateReion 和 Adapt方法。如果有需要骤肛,重寫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;
}
好淑玫,完事巾腕,收工。