AvalonDock使用心得「建议收藏」

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

大家好,又见面了,我是你们的朋友全栈君。

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

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

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

  

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

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

 

ContractedBlock.gif
ExpandedBlockStart.gif
容器布局


     <
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文件都列举出来,为此写了一个通用的方法,给定目录和查找的文件扩展名—>返回相应的文件列表。

   

ContractedBlock.gif
ExpandedBlockStart.gif
读取路径下的指定扩展名的文件



public

static
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


return

null
;
}

catch
(System.Exception
/*
e
*/
)
{


return

null
;
}


return
xmlpaths;
}

 

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

  
ContractedBlock.gif
ExpandedBlockStart.gif
登陆时的的布局部分


    //
登陆窗口


    
private
List
<
SelectionItem
>
layoutlist
=

new
List
<
SelectionItem
>
();
    

public

void
LayoutListInit()
{

SelectionItem item

=

new
SelectionItem() { Name
=


默认布局

};
item.IsEnabled

=

false
;
layoutlist.Add(item);

List
<
string
>
names
=
GlobalMethod.CheckDirectory(AppDomain.CurrentDomain.BaseDirectory,

.xml

);

foreach
(
string
str
in
names)
{


if
(str
!=


SampleLayout

)
layoutlist.Add(

new
SelectionItem() { Name
=
str });
}
}

    
private

void
OKButton_Click(
object
sender, System.Windows.RoutedEventArgs e)
{


if
(operate.HCNet_ServerLoad(ipcombo.Text, servertypecombo.Text,
porttext.Text, namecombo.Text, passwordbox.Password,MainWindow.handle))
{

GlobalData.LayoutList

=
layoutlist;

this
.DialogResult
=

true
;

this
.Close();
}
}
 

//
主窗口


    void
dockManager_Loaded(
object
sender, RoutedEventArgs e)
{


foreach
(SelectionItem item
in
GlobalData.LayoutList)
{


if
(item.IsSelected)
RestoreLayout(item.Name);
}
}


public

static

void
RestoreLayout(
string
layoutname)
{

MainWindow win

=
App.Current.MainWindow
as
MainWindow;

if
(layoutname
==


默认布局

)
{


if
(File.Exists(LayoutFileName))
win.dockManager.RestoreLayout(LayoutFileName);
}

else

{


string
filename
=
layoutname
+


.xml

;

if
(File.Exists(filename))
win.dockManager.RestoreLayout(filename);
}
}

 

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

ContractedBlock.gif
ExpandedBlockStart.gif 界面部分 

<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”
/>

 

ContractedBlock.gif
ExpandedBlockStart.gif
初始化布局列表及控件列表



private

static
ObservableCollection
<
SelectionItem
>
layoutlist
=

new
ObservableCollection
<
SelectionItem
>
();

    private static ObservableCollection<SelectionItem> dockablecontentlist = new ObservableCollection<SelectionItem>();
       private static ObservableCollection<SelectionItem> documentcontentlist = new ObservableCollection<SelectionItem>();
  
    private

void
LayoutListInit()
{
       //GlobalData.LayoutList为调用之前的方法获得的布局文件列表


foreach
(SelectionItem item
in
GlobalData.LayoutList)
{


if
(item.Name
!=


SampleLayout

)
layoutlist.Add(item);
}
}
  

    private

void
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);
}
}


public

void
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
;
}
}


public

void
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
;
}
}
}


public

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

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

ContractedBlock.gif
ExpandedBlockStart.gif
保存布局


  
private

void
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();
  }

 

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

ContractedBlock.gif
ExpandedBlockStart.gif
删除布局



private

void
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文件内容来判断是否是布局文件,暂时还没有想到更好的办法,不知道大家有没有更好的经验呢?!

 四、动态添加控件 

ContractedBlock.gif
ExpandedBlockStart.gif
添加控件


    private

void
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();
}

 

 

五、其他

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

ContractedBlock.gif
ExpandedBlockStart.gif
代码


      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;
};

 

呵呵,一点小小经验,文章也拖了好久才写好,大家见笑啦!

偷懒了,没有写一个更好的Demo给大家,用了一些自己项目里现成的代码,希望有问题的朋友可以多多交流,也请大家多多指教,赐教我一些更好的方法!

转载于:https://www.cnblogs.com/wdysunflower/archive/2010/07/24/1779960.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/162116.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号