桌面程序的应用,不可避免的就会用到大量的布局控件,以前的一个项目也想过去作相似于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、其余
还有这样一个事件时的注意的,其实我也说很差他的本质是什么,感受好像就是每次启动新的布局时,若是以有布局存在空缺或已经关闭的状况下就会到达这里,因此在我在这里将缺失的控件给加上。
呵呵,一点小小经验,文章也拖了很久才写好,你们见笑啦!