zoukankan      html  css  js  c++  java
  • AvalonDock使用心得

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

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

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

      

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

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

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

       

    读取路径下的指定扩展名的文件
    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;
    }

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

      登陆时的的布局部分
        //登陆窗口
        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);
    }
    }

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

    界面部分 
    <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"/>

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

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

    保存布局
       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();
      }

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

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

     四、动态添加控件 

    添加控件
        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();
    }

    五、其他

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

    代码
          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给大家,用了一些自己项目里现成的代码,希望有问题的朋友可以多多交流,也请大家多多指教,赐教我一些更好的方法!

  • 相关阅读:
    OI数学知识清单
    线段树入门教程
    扩展欧几里得定理基础讲解 代码及证明
    名字竞技场 V3.0
    可持久化线段树(主席树)新手向教程
    矩阵乘法浅析
    [Luogu] P1233 木棍加工
    高斯消元 模板
    位运算技巧
    [ZJOJ] 5794 2018.08.10【2018提高组】模拟A组&省选 旅行
  • 原文地址:https://www.cnblogs.com/wdysunflower/p/1779960.html
Copyright © 2011-2022 走看看