桌面程序的應用,不可避免的就會用到大量的佈局控件,以前的一個項目也想過去作相似於Visual Studio的那種靈活的佈局控件,也就是界面上的控件可以實現拖拽放置、隱藏、窗口化等一系列的操做,但因爲開發時間以及需求的緣由,沒有太嚴格要求這方面功能的實現,也就只能算是想過一下而已,實際用的時候仍是固定佈局,可是最近接觸到新的項目,須要這方面的應用就不得不本身動手查找和作這樣的東西了。web
有朋友推薦RadControls裏了控件——RadDocking,下載安裝RadControls後,發現他裏邊的控件的確作的很不錯,並且Demo也很詳細,RadDocking也能知足個人需求,使用也還算方便,可是由於是試用版的,每次程序運行時都會出現相應的提示,嘗試找他的最新版的破解版最終也無果,我的又不屑於用好久以前的版本,並且畢竟不是知根知底的東西,用起來也以爲怪怪的,因此仍是放棄了使用RadDocking。ide
就在我快要放棄尋找,準備有時間本身作的時候(後來發現本身想的有點簡單了),讓我發現了AvalonDock,看了下它的Demo發現效果也不錯,至少看起來可以知足個人應用需求,並且仍是開源的,順便就當研究學習了。你們能夠到http://avalondock.codeplex.com/下載和了解更加詳細的信息。說了這麼多廢話趕忙進入正題吧!佈局
雖然有現成的Demo,但第一次接觸這類控件仍是折騰了很多時間,一點點的摸索它的使用方法!學習
1、最基本的佈局格式,容器的承載:this
仔細看的話就能發現這裏邊有必定的層次關係。 spa
首先須要一個DockingManager來統籌全局,它可以幫忙管理和處理在其範圍內的子級控件的一系列操做——安排載窗格,窗格和處理飛出浮動窗口,以及佈局的保存和恢復。感受好像是隻有同一DockingManager下的各個控件才能互相做用,不一樣DockingManager下的控件是沒法跨界操做的。3d
再就是DockingManager裏放置ResizingPanel,它也是一個容器,用來控制其子控件的佈局方式,其Orientation屬性相似於StackPanel的同名屬性,表示其子級控件是水平或是垂直放置。code
接下來就是兩組東西了,它們是一一對應的,DockablePane與DockableContent對應,DocumentPane與DocumentContent對應,並且都是前者包含後者。DockablePane、DocumentPane均可以也都應該放置到ResizingPanel,具體的佈局方式就看實際應用的須要了,而DockableContent、DocumentContent下包含的就是咱們最終想要呈現給用戶的功能模塊控件了。orm
須要指出的是DockablePane和DocumentPane都繼承至Pane,DockableContent和entContent都繼承至Managedcontent,在後面一些問題的處理上會用的到。視頻
下面就分別是DockablePane和DocumentPane的呈現形式,從界面上也能看出點不一樣的哈,具體的一些不一樣會在下面根據我本身的經驗詳細講解到。
接下來就是一些列針對佈局的處理了。
2、佈局的保存與恢復
這兩部操做其實很簡單,由於DockingManager自身就封裝好了相應的方法——SaveLayout、RestoreLayout。它們均有不一樣的重載形式,便可以傳入不一樣的參數,其中以文件名做爲參數傳入是最方便的一種。
實際應用中,須要用戶登陸時列舉出已有的佈局列表供其選擇,根據其選擇應用相應的佈局,暫時是經過查找應用程序目錄下的xml文件來實現的,就是將該目錄下全部的xml文件都列舉出來,爲此寫了一個通用的方法,給定目錄和查找的文件擴展名—>返回相應的文件列表。
程序有個登錄窗口,須要用戶選擇相應的佈局,根據用戶的選擇應用對應的佈局。列表、集合與界面之間都是經過綁定來實現的,下面不少地方都是相似的用法。 這部分倒沒什麼值得注意的地方,有一點可說的就是要注意區分默認佈局和其餘佈局的處理。
登錄之後在做了如下處理:
<DataTemplate x:Key="LayoutNameListDataTemplate">
<RadioButton x:Name="radioButton" Margin="0,3,0,0" Content="{Binding Name, Mode=Default}" GroupName="LayoutNameList" VerticalContentAlignment="Top" Checked="layoutchose_Checked" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
<Style x:Key="MoreWindowsListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<MenuItem Header="{Binding Name, Mode=Default}" IsCheckable="True" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=Default}"/>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
private void winchange_Checked(object sender, System.Windows.RoutedEventArgs e)
{
MenuItem item = e.OriginalSource as MenuItem;
SelectionItem slitem = item.DataContext as SelectionItem;
if (slitem != null)
{
DockableContent dockablecontent = win.FindName(slitem.Name) as DockableContent;
if (dockablecontent != null)
{
if (dockablecontent.State == DockableContentState.Hidden)
dockablecontent.Show();
return;
}
ManagedContent managecontent = win.FindName(slitem.Name) as ManagedContent;
if (managecontent != null)
{
managecontent.Show();
return;
}
}
}
private void winchang_Unchecked(object sender, System.Windows.RoutedEventArgs e)
{
MenuItem item = e.OriginalSource as MenuItem;
SelectionItem slitem = item.DataContext as SelectionItem;
if (slitem != null)
{
ManagedContent content = win.FindName(slitem.Name) as ManagedContent;
if (content != null)
{
content.Hide();
}
}
}
也是經過綁定集合的方式與界面結合起來。 因爲佈局列表在其餘地方也用獲得,還涉及到添加、刪除等操做,爲了保證界面與數據的實時響應,使用ObservableCollection<>集合來用於綁定。
content.StateChanged += new RoutedEventHandler(dokablecontent_StateChanged);
content.Closed += new EventHandler(content_Closed);
content.Closing += new EventHandler<System.ComponentModel.CancelEventArgs>(content_Closing);
content.Closed += new EventHandler(content_Closed);
以上幾個事件尤爲須要注意,鼠標對界面操做都是經過相應的事件來與列表之間相互響應的。同時空間的顯示與隱藏的實現也在這段代碼裏。
保存佈局時我就採用的是讓用戶輸入佈局名稱,根據該名稱來保存佈局。同時向佈局列表中添加該項。
佈局管理中,對已有的佈局進行刪除操做,刪除列表中的項同時刪除相應的文件。
這樣作可能可能不夠完善,xml文件的查找就是一個問題,之後考慮經過讀取xml文件內容來判斷是不是佈局文件,暫時尚未想到更好的辦法,不知道你們有沒有更好的經驗呢?!
4、動態添加控件
5、其餘
還有這樣一個事件時的注意的,其實我也說很差他的本質是什麼,感受好像就是每次啓動新的佈局時,若是以有佈局存在空缺或已經關閉的狀況下就會到達這裏,因此在我在這裏將缺失的控件給加上。
呵呵,一點小小經驗,文章也拖了很久才寫好,你們見笑啦!