WPF在VisualTree上增加Visual
作為一個(gè)WPF控件開(kāi)發(fā)者,我在工作中經(jīng)常遇到如本文標(biāo)題所示的問(wèn)題。其實(shí),這個(gè)問(wèn)題并不是很難,只是在操作上有些繁瑣。本文將嘗試對(duì)這個(gè)問(wèn)題進(jìn)行解答,并且對(duì)相關(guān)的一些技術(shù)細(xì)節(jié)加以探討。
先從我遇到的一個(gè)典型的問(wèn)題開(kāi)始吧:寫(xiě)一個(gè)MyElement類(lèi),要求如下:
- 從FrameworkElement繼承
- 增加一個(gè)Button到它的VisualTree上
在Visual上有一個(gè)AddVisualChild方法,相信很多剛接觸這個(gè)方法的同學(xué)們(好吧,至少我是這樣)都會(huì)“顧名思義”地認(rèn)為這個(gè)方法就可以解決本文的問(wèn)題。再加上MSDN上也給出了一個(gè)例子來(lái)“火上澆油”一把。于是,一陣竊喜之后,我興奮地敲出了以下代碼:
class MyElement : FrameworkElement { private Button _button = new Button() { Content = "I'm a Button!"}; public MyElement() { this.AssembleVisualChildren(); } private void AssembleVisualChildren() { this.AddVisualChild(this._button); } protected override int VisualChildrenCount { get { return 1; } } protected override Visual GetVisualChild(int index) { return this._button ; } }
然后將這個(gè)MyElement加入測(cè)試窗口,代碼如下:
<Window x:Class="AddVisualChildTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:loc="clr-namespace:AddVisualChildTest" WindowStartupLocation="CenterScreen" Title="Window1" Height="300" Width="300"> <Grid> <loc:MyElement Margin="10"/> </Grid> </Window>
運(yùn)行后的結(jié)果如下:
空空如也!嗯,被忽悠了。一陣失落、打擊之后,我的好奇心被激發(fā)了:這是為什么呢?于是我狂找資料,終于被我發(fā)現(xiàn)了:
實(shí)際上,在上面這個(gè)例子中,AddVisualChild這個(gè)方法只是在MyElement和Button之間建立起了一種VisualTree上的父子關(guān)系,但是并沒(méi)有將Button掛接到MyElement的VisualTree上,所以最終我們沒(méi)有在屏幕上看到這個(gè)Button。
為了將Button真正掛接到MyElement的VisualTree上,還需要額外做一件事情:在VisualTree上為這個(gè)Button分配空間并且指定位置,這個(gè)過(guò)程叫做Layout。此過(guò)程分兩個(gè)部分:一個(gè)是Measure,另一個(gè)是Arrange。這兩個(gè)過(guò)程在FrameworkElement上對(duì)應(yīng)著兩個(gè)方法:MeasureOverride和ArrangeOverride方法。具體做法如下:
protected override Size MeasureOverride(Size availableSize) { if (this.VisualChildrenCount > 0) { UIElement child = this.GetVisualChild(0) as UIElement; Debug.Assert(child != null); // !Assert child.Measure(availableSize); return child.DesiredSize; } return availableSize; } protected override Size ArrangeOverride(Size finalSize) { Rect arrangeRect = new Rect() { Width = finalSize.Width, Height = finalSize.Height }; if (this.VisualChildrenCount > 0) { UIElement child = this.GetVisualChild(0) as UIElement; Debug.Assert(child != null); // !Assert child.Arrange(arrangeRect); } return finalSize; }
再次運(yùn)行程序:
目標(biāo)實(shí)現(xiàn)。
由此,我們可以總結(jié)出這個(gè)問(wèn)題的解決方案如下:
在MyElement的構(gòu)造器中調(diào)用AddVisualChild方法;
重寫(xiě)VisualChildCount屬性;
重寫(xiě)GetVisualChild方法;
重寫(xiě)MeasureOverride方法;
重寫(xiě)ArrangeOverride方法;
另外,WPF在此問(wèn)題的解決上也為開(kāi)發(fā)者提供了一些必要的幫助。就我所知的,有如下幾個(gè)內(nèi)容:
1、Panel
還是本文開(kāi)始提到的問(wèn)題,只不過(guò)要將其中的FrameworkElement換為Panel。除了上面所提到的方法,Panel為我們提供了更加方便的實(shí)現(xiàn)方式。代碼如下:
class MyElement : Panel { private Button _button = new Button() { Content = "I'm a Button!" }; public MyElement() { this.Children.Add(_button); } protected override Size MeasureOverride(Size availableSize) { if (this.VisualChildrenCount > 0) { UIElement child = this.GetVisualChild(0) as UIElement; Debug.Assert(child != null); // !Assert child.Measure(availableSize); return child.DesiredSize; } return availableSize; } protected override Size ArrangeOverride(Size finalSize) { Rect arrangeRect = new Rect() { Width = finalSize.Width, Height = finalSize.Height }; if (this.VisualChildrenCount > 0) { UIElement child = this.GetVisualChild(0) as UIElement; Debug.Assert(child != null); // !Assert child.Arrange(arrangeRect); } return finalSize; } }
之所以能這樣做的原因是Panel已經(jīng)替我們將如下幾個(gè)工作封裝在了UIElementCollection(Panel的Children屬性)中:
AddVisualChild
VisualChildCount
GetVisualChild
2、VisualCollection
另外,在這個(gè)過(guò)程中,我們還可以使用一個(gè)叫做VisualCollection的類(lèi)來(lái)作為所有 Visual Child的容器。這個(gè)容器構(gòu)造的時(shí)候需要一個(gè)Visual類(lèi)型的Parent,然后在添加、刪除Visual Child的時(shí)候,它的相應(yīng)方法(Add,Remove)就會(huì)幫助我們自動(dòng)調(diào)用Parent的AddVisualChild和RemoveVisualChild方法。如此一來(lái),我們的工作量又減少了。
到此這篇關(guān)于WPF在VisualTree上增加Visual的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#中實(shí)體類(lèi)與XML相互轉(zhuǎn)換方式
這篇文章主要介紹了C#中實(shí)體類(lèi)與XML相互轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07詳細(xì)聊聊C#的并發(fā)機(jī)制優(yōu)秀在哪
并發(fā)其實(shí)是一個(gè)很泛的概念,字面意思就是"同時(shí)做多件事",不過(guò)方式有所不同,下面這篇文章主要給大家介紹了關(guān)于C#并發(fā)機(jī)制的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02C#開(kāi)發(fā)WinForm之DataGridView開(kāi)發(fā)詳解
這篇文章主要介紹了C#開(kāi)發(fā)WinForm之DataGridView開(kāi)發(fā)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01C#自定義類(lèi)型強(qiáng)制轉(zhuǎn)換實(shí)例分析
這篇文章主要介紹了C#自定義類(lèi)型強(qiáng)制轉(zhuǎn)換的方法,實(shí)例分析了C#類(lèi)型轉(zhuǎn)換的相關(guān)技巧,需要的朋友可以參考下2015-05-05C#結(jié)合JavaScript實(shí)現(xiàn)多文件上傳功能
在許多應(yīng)用場(chǎng)景里,多文件上傳是一項(xiàng)比較實(shí)用的功能,本文主要為大家詳細(xì)介紹了C#如何結(jié)合JavaScript實(shí)現(xiàn)多文件上傳功能,感興趣的小伙伴可以了解下2023-12-12C#中DataSet,DataTable,DataView的區(qū)別與用法
這篇文章介紹了C#中DataSet,DataTable,DataView的區(qū)別與用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05C#如何使用SHBrowseForFolder導(dǎo)出中文文件夾詳解
這篇文章主要給大家介紹了關(guān)于C#如何使用SHBrowseForFolder導(dǎo)出中文文件夾的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)合作工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11