Docking For WPF–AvalonDock
桌面程序的应用,不可避免的就会用到大量的布局控件,之前的一个项目也想过去做类似于Visual Studio的那种灵活的布局控件,也就是界面上的控件能够实现拖拽放置、隐藏、窗口化等一系列的操作,但由于开发时间以及需求的原因,没有太严格要求这方面功能的实现,也就只能算是想过一下而已,实际用的时候还是固定布局,但是最近接触到新的项目,需要这方面的应用就不得不自己动手查找和做这样的东西了。
有朋友推荐RadControls里了控件——RadDocking,下载安装RadControls后,发现他里边的控件的确做的很不错,而且Demo也很详细,RadDocking也能满足我的需求,使用也还算方便,但是因为是试用版的,每次程序运行时都会出现相应的提示,尝试找他的最新版的破解版最终也无果,个人又不屑于用很久之前的版本,而且毕竟不是知根知底的东西,用起来也觉得怪怪的,所以还是放弃了使用RadDocking。
就在我快要放弃寻找,准备有时间自己做的时候(后来发现自己想的有点简单了),让我发现了AvalonDock,看了下它的Demo发现效果也不错,至少看起来能够满足我的应用需求,而且还是开源的,顺便就当研究学习了。大家可以到http://avalondock.codeplex.com/下载和了解更加详细的信息。说了这么多废话赶紧进入正题吧!
虽然有现成的Demo,但第一次接触这类控件还是折腾了不少时间,一点点的摸索它的使用方法!
一、最基本的布局格式,容器的承载:

<AvalonDock:ResizingPanel>
<AvalonDock:ResizingPanel Orientation="Vertical" AvalonDock:ResizingPanel.ResizeWidth="0.2*">
<AvalonDock:DockablePane AvalonDock:ResizingPanel.ResizeWidth="0.1*">
<AvalonDock:DockableContent x:Name="CameraContent" Title="摄像机" FontFamily="微软雅黑" FloatingWindowSize="250,300">
<VideoMonitor:CameraControl/>
</AvalonDock:DockableContent>
</AvalonDock:DockablePane>
<AvalonDock:DockablePane>
<AvalonDock:DockableContent x:Name="PTZControlContent" Title="云台控制" FontFamily="微软雅黑">
<VideoMonitor:PTZControllerControl/>
</AvalonDock:DockableContent>
</AvalonDock:DockablePane>
<AvalonDock:DockablePane AvalonDock:ResizingPanel.ResizeHeight="*">
<AvalonDock:DockableContent x:Name="PlayOperateContent" Title="回放控制" FontFamily="微软雅黑">
<VideoMonitor:PlayOperateControl/>
</AvalonDock:DockableContent>
</AvalonDock:DockablePane>
</AvalonDock:ResizingPanel>
<AvalonDock:ResizingPanel x:Name="VideoResizingPanel">
<AvalonDock:DocumentPane>
<AvalonDock:DocumentContent x:Name="VideoBroswerContent" Title="视频监控" FontFamily="微软雅黑">
<VideoMonitor:VideoBroswerControl x:Name="VideoBroswer"/>
</AvalonDock:DocumentContent>
</AvalonDock:DocumentPane>
</AvalonDock:ResizingPanel>
</AvalonDock:ResizingPanel>
</AvalonDock:DockingManager>

仔细看的话就能发现这里边有一定的层次关系。
首先需要一个DockingManager来统筹全局,它能够帮忙管理和处理在其范围内的子级控件的一系列操作——安排载窗格,窗格和处理飞出浮动窗口,以及布局的保存和恢复。感觉好像是只有同一DockingManager下的各个控件才能互相作用,不同DockingManager下的控件是无法跨界操作的。
再就是DockingManager里放置ResizingPanel,它也是一个容器,用来控制其子控件的布局方式,其Orientation属性类似于StackPanel的同名属性,表示其子级控件是水平或是垂直放置。
接下来就是两组东西了,它们是一一对应的,DockablePane与DockableContent对应,DocumentPane与DocumentContent对应,而且都是前者包含后者。DockablePane、DocumentPane都可以也都应该放置到ResizingPanel,具体的布局方式就看实际应用的需要了,而DockableContent、DocumentContent下包含的就是我们最终想要呈现给用户的功能模块控件了。
需要指出的是DockablePane和DocumentPane都继承至Pane,DockableContent和entContent都继承至Managedcontent,在后面一些问题的处理上会用的到。
下面就分别是DockablePane和DocumentPane的呈现形式,从界面上也能看出点不同的哈,具体的一些不同会在下面根据我自己的经验详细讲解到。
接下来就是一些列针对布局的处理了。
二、布局的保存与恢复
这两部操作其实很简单,因为DockingManager自身就封装好了相应的方法——SaveLayout、RestoreLayout。它们均有不同的重载形式,即可以传入不同的参数,其中以文件名作为参数传入是最方便的一种。
实际应用中,需要用户登录时列举出已有的布局列表供其选择,根据其选择应用相应的布局,暂时是通过查找应用程序目录下的xml文件来实现的,就是将该目录下所有的xml文件都列举出来,为此写了一个通用的方法,给定目录和查找的文件扩展名—>返回相应的文件列表。

{
List<string> xmlpaths =new List<string>();
try
{
if (!File.Exists(path))
{
if (Directory.Exists(path))
{
string[] paths = Directory.GetFiles(path); //全路径
foreach (string str in paths)
{
if (Path.GetExtension(str) == extension)
xmlpaths.Add(Path.GetFileNameWithoutExtension(str));
}
}
}
else
returnnull;
}
catch (System.Exception /*e*/ )
{
returnnull;
}
return xmlpaths;
}

程序有个登陆窗口,需要用户选择相应的布局,根据用户的选择应用对应的布局。列表、集合与界面之间都是通过绑定来实现的,下面很多地方都是类似的用法。 这部分倒没什么值得注意的地方,有一点可说的就是要注意区分默认布局和其他布局的处理。
登陆以后在作了以下处理:

<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>
<ListBox ItemsSource="{Binding DockableContentList, ElementName=userControl, Mode=Default}" ItemContainerStyle="{DynamicResource MoreWindowsListBoxItemStyle}" MenuItem.Checked="winchange_Checked" MenuItem.Unchecked="winchang_Unchecked"/>


private static ObservableCollection<SelectionItem> dockablecontentlist = new ObservableCollection<SelectionItem>();
private static ObservableCollection<SelectionItem> documentcontentlist = new ObservableCollection<SelectionItem>();
{
foreach (SelectionItem item in GlobalData.LayoutList)
{
if (item.Name !="SampleLayout")
layoutlist.Add(item);
}
}
privatevoid ContentListInit()
{
foreach (DockableContent content in win.dockManager.DockableContents)
{
SelectionItem item =new SelectionItem() { Name = content.Name };
if (!(content.State == DockableContentState.Hidden))
item.IsSelected =true;
dockablecontentlist.Add(item);
content.StateChanged +=new RoutedEventHandler(dokablecontent_StateChanged);
}
foreach (DocumentContent content in win.dockManager.Documents)
{
SelectionItem item =new SelectionItem()
{
Name = content.Name ,
IsSelected =true
};
documentcontentlist.Add(item);
VideoBroswerControl vbcontrol = content.Content as VideoBroswerControl;
if (vbcontrol !=null)
VideoBroswerControl.VideoBroswer = vbcontrol;
content.Closed +=new EventHandler(content_Closed);
content.Closing +=new EventHandler<System.ComponentModel.CancelEventArgs>(content_Closing);
}
foreach (FloatingWindow content in win.dockManager.FloatingWindows)
{
SelectionItem item =new SelectionItem()
{
Name = content.Name,
IsSelected =true
};
dockablecontentlist.Add(item);
content.Closed +=new EventHandler(content_Closed);
}
}
publicvoid content_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
MessageBoxResult result = MessageBox.Show("窗口即将关闭,该操作将导致所有视频关闭,是否继续?", "", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No)
{
ManagedContent content = sender as ManagedContent;
foreach (SelectionItem item in documentcontentlist)
{
if (item.Name == content.Name)
item.IsSelected =true;
}
e.Cancel =true;
}
}
publicvoid dokablecontent_StateChanged(object sender, RoutedEventArgs e)
{
DockableContent content = sender as DockableContent;
if(content.State == DockableContentState.Hidden)
{
foreach (SelectionItem item in dockablecontentlist)
{
if (item.Name == content.Name)
item.IsSelected =false;
}
}
}
publicvoid content_Closed(object sender, EventArgs e)
{
ManagedContent content = sender as ManagedContent;
foreach (SelectionItem item in documentcontentlist)
{
if (item.Name == content.Name)
item.IsSelected =false;
}
}
#endregion
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);
以上几个事件尤其需要注意,鼠标对界面操作都是通过相应的事件来与列表之间相互响应的。同时空间的显示与隐藏的实现也在这段代码里。
保存布局时我就采用的是让用户输入布局名称,根据该名称来保存布局。同时向布局列表中添加该项。

{
MainWindow win = App.Current.MainWindow as MainWindow;
win.dockManager.SaveLayout(layoutname.Text +".xml");
win.toolBar.LayoutList.Add(new SelectionItem() { Name = layoutname.Text });
this.Close();
}

布局管理中,对已有的布局进行删除操作,删除列表中的项同时删除相应的文件。

{
if (layoutList.SelectedItems.Count ==0)
MessageBox.Show("当前没有选中任何布局!");
else
{
MessageBoxResult result = MessageBox.Show("是否删除选中的布局?", "", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
while (layoutList.SelectedItems.Count !=0)
{
System.IO.File.Delete(layoutList.SelectedItems[0].ToString() +".xml");
ToolBarControl tbcontrol =new ToolBarControl();
tbcontrol.LayoutList.Remove((SelectionItem)layoutList.SelectedItems[0]);
}
}
}
}

这样做可能可能不够完善,xml文件的查找就是一个问题,以后考虑通过读取xml文件内容来判断是否是布局文件,暂时还没有想到更好的办法,不知道大家有没有更好的经验呢?!
四、动态添加控件

{
if (!GlobalMethod.TestString(videowinname.Text))
{
MessageBox.Show("名称只能是字母和数字以及下划线,且不能以数字开头!");
videowinname.Text ="";
return;
}
VideoBroswerControl vbcontrol =new VideoBroswerControl();
vbcontrol.LayoutChanged(VideoBroswerControl.layoutnum);
DocumentContent documentContent =new DocumentContent()
{
Name = videowinname.Text,
Title = videowinname.Text,
Content = vbcontrol
};
MainWindow win = App.Current.MainWindow as MainWindow;
win.RegisterName(videowinname.Text, documentContent);
documentContent.Show(win.dockManager);
ToolBarControl tlcontrol =new ToolBarControl();
SelectionItem item =new SelectionItem()
{
Name = videowinname.Text ,
IsSelected =true
};
tlcontrol.DocumentContentList.Add(item);
documentContent.Closed +=new EventHandler(tlcontrol.content_Closed);
documentContent.Closing +=new EventHandler<System.ComponentModel.CancelEventArgs>(tlcontrol.content_Closing);
this.Close();
}

五、其他
还有这样一个事件时的注意的,其实我也说不好他的本质是什么,感觉好像就是每次启动新的布局时,如果以有布局存在空缺或已经关闭的情况下就会到达这里,所以在我在这里将缺失的控件给加上。

{
DockingManager manager = s as DockingManager;
VideoBroswerControl vbcontrol =new VideoBroswerControl();
vbcontrol.LayoutChanged(VideoBroswerControl.layoutnum);
var documentContent =new DocumentContent()
{
Name = e.Name,
Title = e.Name,
Content = vbcontrol
};
e.Content = documentContent;
};

呵呵,一点小小经验,文章也拖了好久才写好,大家见笑啦!
Docking For WPF–AvalonDock的更多相关文章
- 基于WPF系统框架设计(5)-Ribbon整合Avalondock 2.0实现多文档界面设计(二)
AvalonDock 是一个.NET库,用于在停靠模式布局(docking)中排列一系列WPF/WinForm控件.最新发布的版本原生支持MVVM框架.Aero Snap特效并具有更好的性能. Ava ...
- 基于WPF系统框架设计(4)-Ribbon整合Avalondock 2.0实现多文档界面设计(一)
前些时间研究了WPF的一些框架,感觉基于Prism框架的MVVM模式对系统的UI与逻辑分离很好,所以就按照之前Winform的框架设计,用WPF做了一套,感觉比Winform要强很多. MVVM模式和 ...
- WPF实现选项卡效果(2)——动态添加AvalonDock选项卡
原文:WPF实现选项卡效果(2)--动态添加AvalonDock选项卡 简介 在前面一篇文章里面,我们使用AvalonDock实现了类似于VS的选项卡(或者浏览器的选项卡)效果.但是我们是通过xaml ...
- WPF实现选项卡效果(3)——自定义动态添加的AvalonDock选项卡内容
原文:WPF实现选项卡效果(3)--自定义动态添加的AvalonDock选项卡内容 简介 在前面一篇文章里面,我们实现了AvalonDock选项卡的动态添加,但是对于选项卡里面的内容,我们并没有实现任 ...
- WPF实现选项卡效果(1)——使用AvalonDock
原文:WPF实现选项卡效果(1)--使用AvalonDock 简介 公司最近一个项目,软件采用WPF开发,需要实现类似于VS的选项卡(或者是浏览器的选项卡)效果.搜寻诸多资料后,发现很多同仁推荐Ava ...
- AvalonDock 2.0 的简单运用
最近在研究AvalonDock的一些使用,碰到了一些问题.现在拿出来跟大家分享分享. 网上找了一大把AvalonDock 1.3版本的资料,弄出Demo后发现属性面板(DockableContent) ...
- WPF中自定义标题栏时窗体最大化处理之WindowChrome
注意: 本文方法基础是WindowChrome,而WindowChrome在.NET Framework 4.5之后才集成发布的.见:WindowChrome Class 在.NET Framewor ...
- Wpf开源收集
1,到底有哪些开源MVVM框架? 前面介绍了WPF的基本概念和一些相关知识,我们了解到开发WPF应用程序可以使用现成的框架和模式,最为合适的莫过于时下正热的MVVM模式,所以这里我们也列出针对MVVM ...
- WPF 选项卡
1.引用 xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock" 2.xaml代码 <xcad:DockingMa ...
随机推荐
- 50、转自知乎上android开发相见恨晚的接口
原文链接:http://www.zhihu.com/question/33636939 程序员软件开发Android 开发JavaAndroid修改 Android开发中,有哪些让你觉得相 ...
- 我对于js注入的理解
资料:http://blog.csdn.net/gisredevelopment/article/details/41778671 js注入就是在前端利用使用js的地方 在这其中注入你写的js代码 使 ...
- sql语句查询数据库案例
package com.hanqi.test; import java.sql.Connection; import java.sql.PreparedStatement; import java.s ...
- pc和移动端获取滚动条的位置
移动端获取滚动条:document.body.scrollTop pc端获取滚动条:document.documentElement.scrollTop
- Mac 因误使用chmod -R 777 命令更改 /usr/bin 造成终端不能实用,提醒进程已结束的完美解决方案!
1.不用删除任何文件. 2.启动root用户权限 4.在用root用户登进去 5.在root用户中使用终端输入命令 chown root:wheel /usr/bin/login chmod u+s ...
- CodeForces875C[拓扑排序] Codeforces Round #440 [Div2E/Div1C]
只要保存每相邻两行字符串 第一个不同位 即可.然后按照 第一个不同位上的字符有: " 来自下一行的 大于 来自上一行的" 构图,跑拓扑排序即可. 当然要判断一下有没有环构成, 有环 ...
- 练级(train)
练级(train) 试题描述 cxm 在迷宫中练级.迷宫可以看成一个有向图,有向图的每个边上都有怪物.通过每条边并消灭怪物需要花费 \(1\) 单位时间.消灭一个怪物可以得到一定数量的经验值.怪物被消 ...
- BZOJ3144 [Hnoi2013]切糕 【最小割】
题目 输入格式 第一行是三个正整数P,Q,R,表示切糕的长P. 宽Q.高R.第二行有一个非负整数D,表示光滑性要求.接下来是R个P行Q列的矩阵,第z个 矩阵的第x行第y列是v(x,y,z) (1≤x≤ ...
- 团子最大家族(clannad)
团子最大家族(clannad) 题目描述 bx2k有许多五颜六色的萌萌哒团子.每个团子有一种颜色. 他决定将m个团子排成一排.为了美观,他要求任何相邻的两个团子不能有相同的颜色. 因为bx2k很懒,因 ...
- 阿里系产品Xposed Hook检测机制原理分析
阿里系产品Xposed Hook检测机制原理分析 导语: 在逆向分析android App过程中,我们时常用的用的Java层hook框架就是Xposed Hook框架了.一些应用程序厂商为了保护自家a ...