日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

WPF 创建自定义面板

發布時間:2023/12/4 asp.net 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WPF 创建自定义面板 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前面兩個章節分別介紹了兩個自定義控件:自定義的ColorPicker和FlipPanel控件。接下來介紹派生自定義面板以及構建自定義繪圖控件。

  創建自定義面板是一種特殊但較常見的自定義控件開發子集。前面以及介紹過有關面板方面的知識,了解到面板駐留一個或多個子元素,并且實現了特定的布局邏輯以恰當地安排子元素。如果希望構建自己的可拖動的工具欄或可停靠的窗口系統,自定義面板是很重要的元素。當創建需要非標準特定布局的組合控件時,自定義面板通常很有用的,例如停靠工具欄。

  接下里介紹一個基本的Canvas面板部分以及一個增強版本的WrapPanel面板兩個簡單的示例。

一、兩步布局過程

  每個面板都使用相同的設備:負責改變子元素尺寸和安排子元素的兩步布局過程。第一階段是測量階段(measure pass),在這一階段面板決定其子元素希望具有多大的尺寸。第二個階段是排列階段(layout pass),在這一階段為每個控件指定邊界。這兩個步驟是必需的,因為在決定如何分割可用空間時,面板需要考慮所有子元素的期望。

  可以通過重寫名稱為MeasureOverride()和ArrangeOverride()方法,為這兩個步驟添加自己的邏輯,這兩個方法是作為WPF布局系統的一部分在FrameworkElement類中定義的。奇特的名稱使用標識MeasureOverride()和ArrangeOverride()方法代替在MeasureCore()和ArrangeCore()方法中定義的邏輯,后兩個方法在UIElement類中定義的。這兩個方法是不能被重寫的。

  1、MeasureOverride()方法

  第一步是首先使用MeasureOverride()方法決定每個子元素希望多大的空間。然而,即使是在MeasureOverride()方法中,也不能為子元素提供無限空間,至少,也應當將自元素限制在能夠適應面板可用空間的范圍之內。此外,可能希望更嚴格地限制子元素。例如,具有按比例分配尺寸的兩行的Grid面板,會為子元素提供可用高度的一般。StackPanel面板會為第一個元素提供所有可用空間,然后為第二個元素提供剩余的空間等等。

  每個MeasureOverride()方法的實現負責遍歷子元素集合,并調用每個子元素的Measure()方法。當調用Measure()方法時,需要提供邊界框——決定每個子空間最大可用空間的Size對象。在MeasureOverride()方法的最后,面板返回顯示所有子元素所需的空間,并返回它們所期望的尺寸。

  下面是MeasureOverride()方法的基本結構,其中沒有具體的尺寸細節:

protected override Size MeasureOverride(Size constraint) {//Examine all the childrenforeach (UIElement element in base.InternalChildren){//Ask each child how much space it would like,given the//availableSize constraintSize availableSize=new Size{...};element.Measure(availableSize);//(you can now read element.DesiredSize to get the requested size.)}//Indicate how mush space this panel requires.//This will be used to set the DesiredSize property of the panel.return new Size(...); }

  Measure()方法不返回數值。在為每個子元素調用Measure()方法之后,子元素的DesiredSize屬性提供了請求的尺寸。可以在為后續子元素執行計算是(以及決定面板需要的總空間時)使用這一信息。

  因為許多元素直接調用了Measure()方法之后才會渲染它們自身,所以必須為每個子元素調用Measure()方法,即使不希望限制子元素的尺寸或使用DesiredSize屬性也同樣如此。如果希望讓所有子元素能夠自由獲得它們所希望的全部空間,可以傳遞在兩個方向上的值都是Double.PositiveInfinity的Size對象(ScrollViewer是使用這種策略的一個元素,原因是它可以處理任意數量的內容)。然后子元素會返回其中所有內容所需要的空間。否則,子元素通常會返回其中內容需要的空間或可用空間——返回較小值。

  在測量過程的結尾,布局容器必須返回它所期望的尺寸。在簡單的面包中,可以通過組合每個子元素的期望尺寸計算面板所期望的尺寸。

  Measure()方法觸發MeasureOverride()方法。所以如果在一個布局容器中放置另一個布局容器,當調用Measure()方法時,將會得到布局容器及其所有子元素所需要的總尺寸。

  2、ArrangeOverride()方法

  測量完所有元素后,就可以在可用的空間中排列元素了。布局系統調用面板的ArrangeOverride()方法,而面板為每個子元素調用Arrange()方法,以高速子元素為它分配了多大的控件(Arrange()方法會觸發ArrangeOverride()方法,這與Measure()方法會觸發MeasureOverride()方法非常類似).

  當使用Measure()方法測量條目時,傳遞能夠定義可用空間邊界的Size對象。當使用Arrange()方法放置條目時,傳遞能夠定義條目尺寸和位置的System.Windows.Rect對象。這時,就像使用Canvas面板風格的X和Y坐標放置每個元素一樣(坐標確定布局容器左上角與元素左上角之間的距離)。

  下面是ArrangeOverride()方法的基本結構。

protected override Size ArrangeOverride(Size arrangeBounds) {//Examine all the children.foreach(UIElement element in base.InternalChildren){//Assign the child it's bounds.Rect bounds=new Rect(...);element.Arrange(bounds);//(You can now read element.ActualHeight and element.ActualWidth to find out the size it used ..)}//Indicate how much space this panel occupies.//This will be used to set the AcutalHeight and ActualWidth properties//of the panel.return arrangeBounds; }

  當排列元素時,不能傳遞無限尺寸。然而,可以通過傳遞來自DesiredSize屬性值,為元素提供它所期望的數值。也可以為元素提供比所需尺寸更大的空間。實際上,經常會出現這種情況。例如,垂直的StackPanel面板為其子元素提供所請求的高度,但是為了子元素提供面板本身的整個寬度。同樣,Grid面板使用具有固定尺寸或按比例計算尺寸的行,這些行的尺寸可能大于其內部元素所期望的尺寸。即使已經在根據內容改變尺寸的容器中放置了元素,如果使用Height和Width屬性明確設置了元素的尺寸,那么仍可以擴展該元素。

  當使元素比所期望的尺寸更大時,就需要使用HorizontalAlignment和VerticalAlignment屬性。元素內容被放置到指定邊界內部的某個位置。

  因為ArrangeOverride()方法總是接收定義的尺寸(而非無限的尺寸),所以為了設置面板的最終尺寸,可以返回傳遞的Size對象。實際上,許多布局容器就是采用這一步驟來占據提供的所有空間。

二、Canvas面板的副本

  理解這兩個方法的最快捷方法是研究Canvas類的內部工作原理,Canvas是最簡單的布局容器。為了創建自己的Canvas風格的面板,只需要簡單地繼承Panel類,并且添加MeasureOverride()和ArrangeOverride()方法,如下所示:

public class CanvasClone:System.Windows.Controls.Panel{...}

  Canvas面板在他們希望的位置放置子元素,并且為子元素設置它們希望的尺寸。所以,Canvas面板不需要計算如何分割可用空間。這使得MeasureOverride()方法非常簡單。為每個子元素提供無限的空間:

protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) {Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);foreach (UIElement element in base.InternalChildren){element.Measure(size);}return new Size(); }

  注意,MeasureOverride()方法返回空的Size對象。這意味著Canvas 面板根本不請求人和空間,而是由用戶明確地為Canvas面板指定尺寸,或者將其放置到布局容器中進行拉伸以填充整個容器的可用空間。

  ArrangeOverride()方法包含的內容稍微多一些。為了確定每個元素的正確位置,Canvas面板使用附加屬性(Left、Right、Top以及Bottom)。附加屬性使用定義類中的兩個輔助方法實現:GetProperty()和SetProperty()方法。

  下面是用于排列元素的代碼:

protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize){foreach (UIElement element in base.InternalChildren){double x = 0;double y = 0;double left = Canvas.GetLeft(element);if (!DoubleUtil.IsNaN(left)){x = left;}double top = Canvas.GetTop(element);if (!DoubleUtil.IsNaN(top)){y = top;}element.Arrange(new Rect(new Point(x, y), element.DesiredSize));}return finalSize;}

三、更好的WrapPanel面板

  WrapPanel面板執行一個簡單的功能,該功能有有時十分有用。該面板逐個地布置其子元素,一旦當前行的寬度用完,就會切換到下一行。但有時候需要采用一種方法來強制立即換行,以便在新行中啟動某個特定控件。盡管WrapPanel面板原本沒有提供這一功能,但通過創建自定義控件可以方便地添加該功能。只需要添加一個請求換行的附加屬性即可。此后,面板中的子元素可使用該屬性在適當位置換行。

  下面的代碼清單顯示了WrapBreakPanel類,該類添加了LineBreakBeforeProperty附加屬性。當將該屬性設置為true時,這個屬性會導致在元素之前立即換行。

public class WrapBreakPanel : Panel{public static DependencyProperty LineBreakBeforeProperty;static WrapBreakPanel(){FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();metadata.AffectsArrange = true;metadata.AffectsMeasure = true;LineBreakBeforeProperty = DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), metadata);}...}

  與所有依賴項屬性一樣,LineBreakBefore屬性被定義成靜態字段,然后在自定義類的靜態構造函數中注冊該屬性。唯一的區別在于進行注冊時使用的是RegisterAttached()方法而非Register()方法。

  用于LineBreakBefore屬性的FrameworkPropertyMetadata對象明確指定該屬性影響布局過程。所以,無論何時設置該屬性,都會觸發新的排列階段。

  這里沒有使用常規屬性封裝器封裝這些附加屬性,因為不在定義它們的同一個類中設置它們。相反,需要提供兩個靜態方法,這來改那個方法能夠使用DependencyObject.SetValue()方法在任意元素上設置這個屬性。下面是LineBreakBefore屬性需要的代碼:

/// <summary> /// 設置附加屬性值 /// </summary> /// <param name="element"></param> /// <param name="value"></param> public static void SetLineBreakBefore(UIElement element, Boolean value) {element.SetValue(LineBreakBeforeProperty, value); }/// <summary> /// 獲取附加屬性值 /// </summary> /// <param name="element"></param> /// <returns></returns> public static Boolean GetLineBreakBefore(UIElement element) {return (bool)element.GetValue(LineBreakBeforeProperty); }

  唯一保留的細節是當執行布局邏輯時需要考慮該屬性。WrapBreakPanel面板的布局邏輯以WrapPanel面板的布局邏輯為基礎。在測量階段,元素按行排列,從而使面板能夠計算需要的總空間。除非太大或LineBreakBefore屬性被設置為true。否則每個元素都唄添加到當前行中。下面是完整的代碼:

protected override Size MeasureOverride(Size constraint){Size currentLineSize = new Size();Size panelSize = new Size();foreach (UIElement element in base.InternalChildren){element.Measure(constraint);Size desiredSize = element.DesiredSize;if (GetLineBreakBefore(element) ||currentLineSize.Width + desiredSize.Width > constraint.Width){// Switch to a new line (either because the element has requested it// or space has run out).panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);panelSize.Height += currentLineSize.Height;currentLineSize = desiredSize;// If the element is too wide to fit using the maximum width of the line,// just give it a separate line.if (desiredSize.Width > constraint.Width){panelSize.Width = Math.Max(desiredSize.Width, panelSize.Width);panelSize.Height += desiredSize.Height;currentLineSize = new Size();}}else{// Keep adding to the current line.currentLineSize.Width += desiredSize.Width;// Make sure the line is as tall as its tallest element.currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);}}// Return the size required to fit all elements.// Ordinarily, this is the width of the constraint, and the height// is based on the size of the elements.// However, if an element is wider than the width given to the panel,// the desired width will be the width of that line.panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);panelSize.Height += currentLineSize.Height;return panelSize;}

  上面代碼中的重要細節是檢查LineBreakBefore屬性。這實現了普遍WrapPanel面板沒有提供的額外邏輯。

  ArrangeOverride()方法的代碼幾乎相同。區別在于:面板在開始布局一行之前需要決定該行的最大高度(根據最高的元素確定)。這樣,每個元素可以得到完整數量的可用空間,可用控件占用行的整個高度。與使用普通的WrapPanel面板進行布局時的過程相同。下面是完整的代碼:

protected override Size ArrangeOverride(Size arrangeBounds){int firstInLine = 0;Size currentLineSize = new Size();double accumulatedHeight = 0;UIElementCollection elements = base.InternalChildren;for (int i = 0; i < elements.Count; i++){Size desiredSize = elements[i].DesiredSize;if (GetLineBreakBefore(elements[i]) || currentLineSize.Width + desiredSize.Width > arrangeBounds.Width) //need to switch to another line{arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i);accumulatedHeight += currentLineSize.Height;currentLineSize = desiredSize;if (desiredSize.Width > arrangeBounds.Width) //the element is wider then the constraint - give it a separate line{arrangeLine(accumulatedHeight, desiredSize.Height, i, ++i);accumulatedHeight += desiredSize.Height;currentLineSize = new Size();}firstInLine = i;}else //continue to accumulate a line{currentLineSize.Width += desiredSize.Width;currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);}}if (firstInLine < elements.Count)arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, elements.Count);return arrangeBounds;}private void arrangeLine(double y, double lineHeight, int start, int end){double x = 0;UIElementCollection children = InternalChildren;for (int i = start; i < end; i++){UIElement child = children[i];child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineHeight));x += child.DesiredSize.Width;}}

  WrapBreakPanel面板使用起來十分簡便。下面的一些標記演示了使用WrapBreakPanel面板的一個示例。在該例中,WrapBreakPanel面板正確地分割行,并且根據其子元素的尺寸計算所需的尺寸:

<Window x:Class="CustomControlsClient.WrapBreakPanelTest"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:lib="clr-namespace:CustomControls;assembly=CustomControls"Title="WrapBreakPanelTest" Height="300" Width="300"><StackPanel><StackPanel.Resources><Style TargetType="{x:Type Button}"><Setter Property="Margin" Value="3"></Setter><Setter Property="Padding" Value="5"/></Style></StackPanel.Resources><TextBlock Padding="5" Background="LightGray">Content above the WrapBreakPanel.</TextBlock><lib:WrapBreakPanel><Button>No Break Here</Button><Button>No Break Here</Button><Button>No Break Here</Button><Button>No Break Here</Button><Button lib:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold">Button with Break</Button><Button>No Break Here</Button><Button>No Break Here</Button><Button>No Break Here</Button><Button>No Break Here</Button></lib:WrapBreakPanel><TextBlock Padding="5" Background="LightGray">Content below the WrapBreakPanel.</TextBlock></StackPanel> </Window>

  下圖顯示了如何解釋上面的標記:

往期精彩回顧



  • 【.net core】電商平臺升級之微服務架構應用實戰

  • .Net Core微服務架構技術棧的那些事

  • Asp.Net Core 中IdentityServer4 授權中心之應用實戰

  • Asp.Net Core 中IdentityServer4 授權中心之自定義授權模式

  • Asp.Net Core 中IdentityServer4 授權流程及刷新Token

  • Asp.Net Core 中IdentityServer4 實戰之 Claim詳解

  • Asp.Net Core 中IdentityServer4 實戰之角色授權詳解

  • Asp.Net Core 中間件應用實戰中你不知道的那些事

  • Asp.Net Core Filter 深入淺出的那些事-AOP

  • Asp.Net Core EndPoint 終結點路由工作原理解讀

  • ASP.NET CORE 內置的IOC解讀及使用


總結

以上是生活随笔為你收集整理的WPF 创建自定义面板的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。