Docking For WPF–AvalonDock

摘要:
以前的一个项目还希望制作类似于Visual Studio的灵活布局控件。你可以去http://avalondock.codeplex.com/下载并了解更多信息。Name=“dockManager”网格。Row=“2”Margin=“0,<Name=“PTZControlContent”Title=“PTZ Control”FontFamily=“Microsoft Yahei”>

桌面程序的应用,不可避免的就会用到大量的布局控件,之前的一个项目也想过去做类似于Visual Studio的那种灵活的布局控件,也就是界面上的控件能够实现拖拽放置、隐藏、窗口化等一系列的操作,但由于开发时间以及需求的原因,没有太严格要求这方面功能的实现,也就只能算是想过一下而已,实际用的时候还是固定布局,但是最近接触到新的项目,需要这方面的应用就不得不自己动手查找和做这样的东西了。

  有朋友推荐RadControls里了控件——RadDocking,下载安装RadControls后,发现他里边的控件的确做的很不错,而且Demo也很详细,RadDocking也能满足我的需求,使用也还算方便,但是因为是试用版的,每次程序运行时都会出现相应的提示,尝试找他的最新版的破解版最终也无果,个人又不屑于用很久之前的版本,而且毕竟不是知根知底的东西,用起来也觉得怪怪的,所以还是放弃了使用RadDocking。

  就在我快要放弃寻找,准备有时间自己做的时候(后来发现自己想的有点简单了),让我发现了AvalonDock,看了下它的Demo发现效果也不错,至少看起来能够满足我的应用需求,而且还是开源的,顺便就当研究学习了。大家可以到http://avalondock.codeplex.com/下载和了解更加详细的信息。说了这么多废话赶紧进入正题吧!

  Docking For WPF–AvalonDock第1张

  虽然有现成的Demo,但第一次接触这类控件还是折腾了不少时间,一点点的摸索它的使用方法!

  一、最基本的布局格式,容器的承载:

 

Docking For WPF–AvalonDock第2张
复制代码
     <AvalonDock:DockingManager x:Name="dockManager" Grid.Row="2" Margin="0,3,0,0">
<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文件都列举出来,为此写了一个通用的方法,给定目录和查找的文件扩展名—>返回相应的文件列表。

   

Docking For WPF–AvalonDock第2张
复制代码
publicstatic List<string> CheckDirectory(string path, string extension)
{
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;
}
复制代码

 

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

  Docking For WPF–AvalonDock第8张登陆时的的布局部分

 

  登陆以后在作了以下处理:

Docking For WPF–AvalonDock第2张界面部分 
复制代码
<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 LayoutList, ElementName=userControl, Mode=Default}" ItemTemplate="{DynamicResource LayoutNameListDataTemplate}"/>

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

复制代码

 

Docking For WPF–AvalonDock第2张
复制代码
privatestatic ObservableCollection<SelectionItem> layoutlist =new ObservableCollection<SelectionItem>();
    private static ObservableCollection<SelectionItem> dockablecontentlist = new ObservableCollection<SelectionItem>();
       private static ObservableCollection<SelectionItem> documentcontentlist = new ObservableCollection<SelectionItem>();
  
    privatevoid LayoutListInit()
{
       //GlobalData.LayoutList为调用之前的方法获得的布局文件列表

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);
  以上几个事件尤其需要注意,鼠标对界面操作都是通过相应的事件来与列表之间相互响应的。同时空间的显示与隐藏的实现也在这段代码里。

  保存布局时我就采用的是让用户输入布局名称,根据该名称来保存布局。同时向布局列表中添加该项。   

Docking For WPF–AvalonDock第2张
复制代码
   privatevoid ok_Click(object sender, System.Windows.RoutedEventArgs e)
   {
MainWindow win
= App.Current.MainWindow as MainWindow;
win.dockManager.SaveLayout(layoutname.Text
+".xml");
win.toolBar.LayoutList.Add(
new SelectionItem() { Name = layoutname.Text });
this.Close();
  }
复制代码

 

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

Docking For WPF–AvalonDock第2张
复制代码
privatevoid delect_Click(object sender, System.Windows.RoutedEventArgs e)
{
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文件内容来判断是否是布局文件,暂时还没有想到更好的办法,不知道大家有没有更好的经验呢?!

 四、动态添加控件 

Docking For WPF–AvalonDock第2张
复制代码
    privatevoid ok_Click(object sender, System.Windows.RoutedEventArgs e)
{
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();
}
复制代码

 

 

五、其他

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

Docking For WPF–AvalonDock第2张
复制代码
      dockManager.DeserializationCallback += (s, e) =>
{
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》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇内存数据的读取高维特征降维方法-随机映射下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

WPF利用Image实现图片按钮

  之前有一篇文章也是采用了Image实现的图片按钮,不过时间太久远了,忘记了地址。好吧,这里我进行了进一步的改进,原来的文章中需要设置4张图片,分别为可用时,鼠标悬浮时,按钮按下时,按钮不可用时的图片,这里我只用了一张图片,利用C#的图片灰度处理自动获得不可用时的图片,利用图片的间距实现悬浮及按下效果。先上效果:(正常 悬浮 按下 不可用)   代码其...

C# Wpf集合双向绑定

说明: msdn中   ObservableCollection<T> 类    表示一个动态数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。 在许多情况下,所使用的数据是对象的集合。 例如,数据绑定中的一个常见方案是使用 ItemsControl(如 ListBox、ListView 或 TreeView)来显示记录的集合。 可...

对List中每个对象元素按时间顺序排序

1 import java.util.*; 2 3 public class ListSort { 4 public static class UserBean { 5 private String id; 6 private String birthday; 7...

Java List与树的互转

  平时工作中都会遇到包含层级关系的List数据转换成树形结构,或者数据已是树形结构了,需要我们处理成普通的单层list结构。以下代码均为本人实际开发所写代码,可能不是最优解、复杂度也比较高,在此和大家一起分享学习!   注:该工具类支持将list转换成树/森林。可自行测试,有疑问或更优方案,可私聊我。     TreeNode@Data @JsonInc...

fancyBox简单入门

1. 下载 fancyBox,解压后根据需要将文件复制到网页文件夹中(建议不要更改目录结构),并在网页源码中引入相应的 css 样式和 js 文件(如果更改了目录结构,引入的时候请调整相应代码,对应它们所在的路径)。注意:别忘了还要先加载 jQuery 库! <!-- 加载 jQuery 库(必须) --> <script type="t...

JPA学习---第九节:JPA中的一对多双向关联与级联操作

一、一对多双向关联与级联操作 1、创建项目,配置文件代码如下: <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http:...