桌面程序的應用,不可避免的就會用到大量的布局控件,之前的一個項目也想過去做類似于Visual Studio的那種靈活的布局控件,也就是界面上的控件能夠實現拖拽放置、隱藏、窗口化等一系列的操作,但由于開發時間以及需求的原因,沒有太嚴格要求這方面功能的實現,也就只能算是想過一下而已,實際用的時候還是固定布局,但是最近接觸到新的項目,需要這方面的應用就不得不自己動手查找和做這樣的東西了。
有朋友推薦RadControls里了控件——RadDocking,下載安裝RadControls后,發現他里邊的控件的確做的很不錯,而且Demo也很詳細,RadDocking也能滿足我的需求,使用也還算方便,但是因為是試用版的,每次程序運行時都會出現相應的提示,嘗試找他的最新版的激活成功教程版最終也無果,個人又不屑于用很久之前的版本,而且畢竟不是知根知底的東西,用起來也覺得怪怪的,所以還是放棄了使用RadDocking。
就在我快要放棄尋找,準備有時間自己做的時候(后來發現自己想的有點簡單了),讓我發現了AvalonDock,看了下它的Demo發現效果也不錯,至少看起來能夠滿足我的應用需求,而且還是開源的,順便就當研究學習了。大家可以到http://avalondock.codeplex.com/下載和了解更加詳細的信息。說了這么多廢話趕緊進入正題吧!
雖然有現成的Demo,但第一次接觸這類控件還是折騰了不少時間,一點點的摸索它的使用方法!
一、最基本的布局格式,容器的承載:
<
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文件都列舉出來,為此寫了一個通用的方法,給定目錄和查找的文件擴展名—>返回相應的文件列表。
return
xmlpaths;
}
程序有個登陸窗口,需要用戶選擇相應的布局,根據用戶的選擇應用對應的布局。列表、集合與界面之間都是通過綁定來實現的,下面很多地方都是類似的用法。這部分倒沒什么值得注意的地方,有一點可說的就是要注意區分默認布局和其他布局的處理。
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 DockableContentList, ElementName=userControl, Mode=Default}
“
ItemContainerStyle
=”
{DynamicResource MoreWindowsListBoxItemStyle}
“
MenuItem.Checked
=”winchange_Checked”
MenuItem.Unchecked
=”winchang_Unchecked”
/>
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);
以上幾個事件尤其需要注意,鼠標對界面操作都是通過相應的事件來與列表之間相互響應的。同時空間的顯示與隱藏的實現也在這段代碼里。
保存布局時我就采用的是讓用戶輸入布局名稱,根據該名稱來保存布局。同時向布局列表中添加該項?! ?/p>
保存布局
布局管理中,對已有的布局進行刪除操作,刪除列表中的項同時刪除相應的文件。
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文件內容來判斷是否是布局文件,暫時還沒有想到更好的辦法,不知道大家有沒有更好的經驗呢?!
四、動態添加控件
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();
}
五、其他
還有這樣一個事件時的注意的,其實我也說不好他的本質是什么,感覺好像就是每次啟動新的布局時,如果以有布局存在空缺或已經關閉的情況下就會到達這里,所以在我在這里將缺失的控件給加上。
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
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
北京到西寧的火車臥鋪票是多少?T175快車北京西-西寧西硬座208元硬臥355/367/379元T151次特快北京西-西寧西硬座238元硬臥401/416/430元T27次特快北京西-西寧西硬座208元硬臥355/367/379元這些都是正常票價,學生半價。北京西始發站14:23西寧航站樓第二天15:232092公里坐臥鋪,安心睡覺,車票由列車員保管,換號換牌子,站前半小時準備好。北京西-西寧的硬...
我的IPHONE怎么升級ISO7?越獄后的iphone只能通過itunes來升級,截至2015/9/26,只能升級到IOS9,不能升級IOS7,升級步驟:1、iphone關機狀態,使用數據線連接電腦,打開電腦的iTunes軟件。2、按住Power鍵2秒。3、在不放開Power鍵的狀態下,按Home 鍵10秒,強制關機。4、不放開Home鍵,輕按Power鍵1次。保持不放開Home鍵15秒左右,手機...
京東極速版怎么qq登錄?京東極速版直接登錄的方法是必須要下載京東極速版APP,然后再再點擊設置里在其中,不能找到登錄界面,然后再再點直接登錄就能完成了我在手機qq我的錢包里面有個京東商城,我買了一樣東西,卻不能跟蹤訂單也找不到訂單,請問這是怎么回事?最關鍵的辦法是聯系聯系京東商城客服,畢竟銀行卡早劃賬只能說明也申請支付,剩是商城確認確認發貨的問題了為什么其它網頁都能開,就是京東商城網頁打不開?簡單...