WPF實(shí)現(xiàn)自定義Panel面板的示例詳解
WPF中的Panel(面板),是繼承自FrameworkElement的抽象類,表示一個(gè)可以用來排列子元素的面板。
在WPF中,一種預(yù)設(shè)了幾種常用的面板,如Grid、StackPanel、WrapPanel等。他們都是繼承自Panel類,并擁有自己的排列子元素的邏輯。
因此,想要自定義一個(gè)Panel,核心的問題就是如何排列子元素。
例如,我們做一個(gè)軸排列的元素,即元素沿著X、Y軸排列。我們定義,以面板寬度(Width)或者高度(Height)的中點(diǎn),為坐標(biāo)原點(diǎn),水平向右為X軸,垂直向下位Y軸。如下圖所示

先上源代碼:
后代代碼:
public class AxisPanel:Panel
{
internal static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
"Oritentation",typeof(Orientation),typeof(AxisPanel),new FrameworkPropertyMetadata(Orientation.Horizontal,FrameworkPropertyMetadataOptions.AffectsArrange));
/// <summary>
/// 指示子元素的布局方向
/// </summary>
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <summary>
/// 測(cè)量子元素,以確定自己的DesiredSize。
/// 此方法由父級(jí)元素自動(dòng)調(diào)用
/// </summary>
/// <param name="availableSize">父級(jí)元素提供給該面板的可用空間尺寸</param>
/// <returns>此面板對(duì)象需要的尺寸(DesiredSize)</returns>
protected override Size MeasureOverride(Size availableSize)
{
double width = 0, height = 0;
switch(Orientation)
{
case Orientation.Horizontal:
// 水平布局
foreach(UIElement child in Children)
{
child.Measure(availableSize);
width += child.DesiredSize.Width; // 計(jì)算面板需要的寬度;
if(child.DesiredSize.Height > height) // 計(jì)算面板需要的高度:為所有元素中最大的高度
{
height = child.DesiredSize.Height;
}
}
break;
case Orientation.Vertical:
// 垂直布局
foreach(UIElement child in Children)
{
child.Measure(availableSize);
height += child.DesiredSize.Height; // 計(jì)算面板需要的高度
if(child.DesiredSize.Width > width)
{
width = child.DesiredSize.Width; // 計(jì)算面板需要寬度:為所有元素中最大的寬度
}
}
break;
}
return new Size(width, height);
}
/// <summary>
/// 排列子元素
/// 此方法由父級(jí)元素自動(dòng)調(diào)用
/// </summary>
/// <param name="finalSize">父級(jí)元素提供給該面板用于布局的最終尺寸。</param>
/// <returns></returns>
protected override Size ArrangeOverride(Size finalSize)
{
double totalWidth = 0, totalHeight = 0; // 面板的總寬度、高度
switch(Orientation)
{
case Orientation.Horizontal: // 水平排列:需要計(jì)算子元素左上角的坐標(biāo)點(diǎn),已經(jīng)用于排列子元素的區(qū)域大小
double x1, y1; // 子元素左上角點(diǎn)的坐標(biāo)
for (int i=0;i<Children.Count;i++)
{
x1 = totalWidth; // 子元素的x坐標(biāo),應(yīng)該為總寬度的值
y1 = (finalSize.Height - Children[i].DesiredSize.Height) / 2; // 子元素的y坐標(biāo),始終保存在軸上
totalWidth += Children[i].DesiredSize.Width; // 總寬度增加
Rect rect = new(x1, y1, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height); // 放置子元素的矩形區(qū)域
Children[i].Arrange(rect); // 放置子元素
}
break;
case Orientation.Vertical:
double x2, y2=0;
for (int i = 0; i < Children.Count;i++)
{
x2 = (finalSize.Width - Children[i].DesiredSize.Width) / 2;
y2 = totalHeight;
totalHeight += Children[i].DesiredSize.Height; // 所有子元素的總高度
Rect rect2 = new(x2, y2, Children[i].DesiredSize.Width, Children[i].DesiredSize.Height);
Children[i].Arrange(rect2);
}
break;
}
return finalSize;
}
}前端代碼:
<rayPanel:AxisPanel Orientation="Horizontal">
<Button Content="Button1" Width="100" Height="40"/>
<Button Content="Button2" Width="100" Height="40" Canvas.Left="100"/>
<Button Content="Button3" Width="100" Height="40" Canvas.Left="200"/>
<TextBlock Text="你在么?"/>
</rayPanel:AxisPanel>顯示效果如下:
當(dāng)Orientation = “Horizontal”

當(dāng)Orientation="Vertical"

名詞簡(jiǎn)寫:
元素:表示一個(gè)繼承自UIElement類的實(shí)例。
Frame元素:表示一個(gè)繼承自FrameworkElement類的實(shí)例。
因?yàn)镕rameworkElement是繼承自UIElement的,因此在很多時(shí)候,一個(gè)FrameworkElement也可以被稱為元素。只有在涉及到FrameworkElement類自己的屬性、方法時(shí),會(huì)被稱為Frame元素。
WPF的布局,分為兩個(gè)步驟:
1. 測(cè)量:確定子元素需要的尺寸(DesiredSize)。
2. 排列:確定子元素的位置,大小。
1. 測(cè)量:
在UIElement中,定義了兩個(gè)方法:Measure() 和 MeasureCore()。在FrameworkElement中還定義了MeasureOverride()方法。他們的工作方式是:
1). 父級(jí)元素調(diào)用子元素實(shí)例的Measure(),此時(shí)Measure()方法會(huì)做一堆的邏輯檢查,然后調(diào)用其MeasureCore()方法。在MeasureCore()方法中,真正的去計(jì)算元素的期望尺寸(DesiredSize)。在UIElement類中,MeasureCore()返回的尺寸永遠(yuǎn)是(0,0)(請(qǐng)看微軟的源代碼:UIElement.MeasureCore())。這意味著如果子元素是一個(gè)UIElement的實(shí)例,則其期望尺寸永遠(yuǎn)會(huì)是(0,0)。
2).FrameworkElement繼承了UIElement,重寫并密封了MeasureCore()方法。因此,當(dāng)調(diào)用子元素的Measure()方法時(shí),子元素的Measure()方法實(shí)際上會(huì)調(diào)用重寫后的MeasureCore()方法,以計(jì)算Frame元素的期望尺寸。
3).在FrameworkElement重寫的MeasureCore()方法中,實(shí)際上又會(huì)調(diào)用MeasureOverride()方法。MeasureOverride()方法是一個(gè)虛方法,可供用戶重寫,以實(shí)現(xiàn)用戶自己的測(cè)量邏輯,同時(shí)還能保證相關(guān)的檢查不被遺漏。
2. 排列:
排列的工作方式,與“測(cè)量”是一樣的。不再贅述。
由此可知,要實(shí)現(xiàn)自己的布局,就有三個(gè)選擇:
1)繼承自UIElement,并重寫MeasureCore()和ArrangeCore()方法(并添加需要的屬性等)。此路徑難度很大。
2)繼承自FrameworkElement,并重寫MeasureOverride()和ArrangeOverride()方法(并添加需要的屬性等)。此路徑難度不小。
3)繼承自Panel,并重寫MeasureOverride()和ArrageOverride()方法(并添加需要的屬性等)。此路徑難度最小。因?yàn)镻anel類已經(jīng)定義了很多相關(guān)的屬性、方法可共用戶使用。
參數(shù)的意義
在MeasureOverride()和ArrageOverride()方法中,兩個(gè)參數(shù)及返回值的意義是不同的:
MeasureOverride():該方法的參數(shù)availableSize,表示父元素,可以用來布局該元素的可用空間。此參數(shù)由WPF布局系統(tǒng)確定;返回值為該元素期望的尺寸(DesiredSize)。自定義面板中,如果用戶不重寫該方法(實(shí)現(xiàn)自己的測(cè)量方式),則會(huì)返回一個(gè)(0,0)的期望尺寸。
ArrangeOverride():該方法的參數(shù)finalSize,表示父元素,最終分配給該元素,用于排列子元素的尺寸。當(dāng)WPF的布局系統(tǒng)測(cè)量完各控件的尺寸后,開始依次排列子元素。當(dāng)排列到本元素時(shí),剩余的空間可能與MeasureOverride()方法中的availableSize不同。因此此時(shí)傳遞進(jìn)來的,是可以用于排列子元素的最終的空間大小。在ArrangeOverride()方法中的,可以按照自己的邏輯去排列子元素。并最終返回一個(gè)尺寸值(Size類型),告訴WPF的布局系統(tǒng),該元素最終使用了多少空間。
以上就是WPF實(shí)現(xiàn)自定義Panel面板的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于WPF自定義面板的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#項(xiàng)目找不到命名空間問題的排查記錄與解決方案
代碼從 Git 倉(cāng)庫(kù)克隆下來后,NuGet 顯示包已安裝,但編譯時(shí)大量 CS0246,PdfSharp 全部找不到,本文完整復(fù)盤問題現(xiàn)象、原因分析與最終解決方案,供以后自己和同事快速定位,需要的朋友可以參考下2026-01-01
C#中Array的存儲(chǔ)結(jié)構(gòu)簡(jiǎn)單介紹
本文將從一個(gè)數(shù)組的基礎(chǔ)操作開始,逐步來推導(dǎo)數(shù)組的在C#基礎(chǔ)操作、數(shù)組在CoreCLR的維護(hù)策略,數(shù)組在C++的內(nèi)存分配等階段具體是如何實(shí)現(xiàn)的,感興趣的朋友跟隨小編一起看看吧2023-11-11
c#使用nsoup解析html亂碼解決方法分享 nsoup教程
NSoup是JSoup的Net移植版本。使用方法基本一致。如果項(xiàng)目涉及HTML的處理,強(qiáng)烈推薦NSoup。但是遺憾的是NSoup默認(rèn)的編碼是UTF-8,處理中文有亂碼,下面給出二種解決方法2014-01-01
WPF+SkiaSharp實(shí)現(xiàn)自繪彈幕效果
這篇文章主要為大家詳細(xì)介紹了如何利用WPF和SkiaSharp實(shí)現(xiàn)自制彈幕效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以了解一下2022-09-09
C#提取PDF表單數(shù)據(jù)的實(shí)現(xiàn)流程
PDF表單是一種常見的數(shù)據(jù)收集工具,廣泛應(yīng)用于調(diào)查問卷、業(yè)務(wù)合同等場(chǎng)景,憑借出色的跨平臺(tái)兼容性和標(biāo)準(zhǔn)化特點(diǎn),PDF表單在各行各業(yè)中得到了廣泛應(yīng)用,本文將探討如何使用C# 實(shí)現(xiàn)自動(dòng)化PDF表單數(shù)據(jù)提取流程,需要的朋友可以參考下2025-01-01
C#探秘系列(四)——GetHashCode,ExpandoObject
這篇繼續(xù)分享下GetHashCode和ExpandoObject這兩個(gè)比較好玩的方法。2014-05-05

