我的面板我做主 -- 淘寶UWP中自定義Panel的實現

来源:http://www.cnblogs.com/ms-uap/archive/2016/03/30/5292929.html
-Advertisement-
Play Games

在Windows10 UWP開發平臺上內置的XMAL佈局面板包括RelativePanel、StackPanel、Grid、VariableSizedWrapGrid 和 Canvas。在開發淘寶UWP應用時,遇到以下業務場景。 業務場景 場景一:淘寶商品提供的一些消費者保障服務 場景二:淘寶商品的 ...


在Windows10 UWP開發平臺上內置的XMAL佈局面板包括RelativePanelStackPanelGridVariableSizedWrapGridCanvas。在開發淘寶UWP應用時,遇到以下業務場景。

業務場景



場景一:淘寶商品提供的一些消費者保障服務

image

場景二:淘寶商品的SKU屬性展示

image

實現分析


系統預設的面板容器控制項顯然不符合要求了。在WPF裡面有WrapPanel,但是在UWP應用裡面沒有,這個時候就需要自定義個Panel了來實現WrapPanel的功能,實現起來不是很複雜。在MSDN的文檔上已經給出了詳細的實現說明:Xaml自定義面板,主要就是自定義一個Panel的派生類,然後重寫(MeasureOverrideArrangeOverride)方法。

以下是MSDN上對兩個方法的解釋說明

MeasureOverride方法

MeasureOverride 方法有返回值,當 Measure 方法在面板上受到佈局中的父元素調用時,佈局系統將使用該值作為面板自身的起始 DesiredSize。方法內的邏輯選擇與它返回的內容同等重要,而且邏輯經常影響返回的值。

所有 MeasureOverride 實現應當迴圈訪問 Children,並且對每個子元素調用 Measure 方法。調用 Measure 方法可為DesiredSize 屬性創建值。這可能會通知面板本身需要多少空間,以及如何在元素間劃分空間或為特定的子元素調整大小。

以下是 MeasureOverride 方法非常基本的框架:

protected override Size MeasureOverride(Size availableSize)
{
    Size returnSize; //TODO might return availableSize, might do something else
     
    //loop through each Child, call Measure on each
    foreach (UIElement child in Children)
    {
        child.Measure(new Size()); // TODO determine how much space the panel allots for this child, that's what you pass to Measure
        Size childDesiredSize = child.DesiredSize; //TODO determine how the returned Size is influenced by each child's DesiredSize
        //TODO, logic if passed-in Size and net DesiredSize are different, does that matter?
    }
    return returnSize;
}

ArrangeOverride方法

ArrangeOverride 方法有 Size 返回值,當 Arrange 在面板上受到佈局中的父元素調用時,佈局系統將在呈現面板本身時使用該值。通常輸入 finalSize 和 ArrangeOverride 返回的 Size 相同。如果不相同,這意味著面板正嘗試將自己調整為不同的大小,而不是佈局中的其他參與者聲明可用的大小。最終大小基於之前已通過面板代碼運行佈局的度量傳遞,這是通常不返回不同大小的原因:這意味著你在故意忽略度量邏輯。

不要返回具有 Infinity 組件的 Size。嘗試使用這樣的 Size 將從內部佈局引發異常。

所有 ArrangeOverride 實現應當迴圈訪問 Children,並且對每個子元素調用 Arrange 方法。和 Measure 一樣,Arrange 沒有返回值。與 Measure 不同,經計算的屬性不會設置為結果(但是, 問題中的元素通常引發 LayoutUpdated 事件)。

以下是 ArrangeOverride 方法非常基本的框架:

protected override Size ArrangeOverride(Size finalSize)
{
    //loop through each Child, call Arrange on each
    foreach (UIElement child in Children)
    {
        Point anchorPoint = new Point(); //TODO more logic for topleft corner placement in your panel
       // for this child, and based on finalSize or other internal state of your panel
        child.Arrange(new Rect(anchorPoint, child.DesiredSize)); //OR, set a different Size 
    }
    return finalSize; //OR, return a different Size, but that's rare
}

創建自定義Panel控制項


下麵用一個簡單的demo演示一下,就知道這兩個方法的作用了。

首先新建一個MyPanel類繼承自Panel類,將Mypanel的背景色設置成灰色,在MyPanel裡面放入6個Border控制項,每個Border控制項設置不同的背景顏色,固定Width和Height為100或者200,方便查看各個控制項的大小區域。

UI xaml:

<Page
    x:Class="AppArrange.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AppArrange"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <local:MyPanel Background="Gray" HorizontalAlignment="Left" VerticalAlignment="Top">
            <Border  Background="Red"  Width="100" Height="100">
                <TextBlock Text="1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
            <Border  Background="Green" BorderThickness="1" Width="100" Height="200">
                <TextBlock Text="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
            <Border  Background="Yellow"  Width="200" Height="100">
                <TextBlock Text="3" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
            <Border  Background="OrangeRed"  Width="100" Height="100">
                <TextBlock Text="4" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
            <Border  Background="Orange"  Width="100" Height="100">
                <TextBlock Text="5" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
            <Border  Background="Orchid"  Width="100" Height="100">
                <TextBlock Text="6" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="32"/>
            </Border>
        </local:MyPanel>
    </Grid>
</Page>

Code behind:

public class MyPanel : Panel
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            return base.MeasureOverride(availableSize);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            return base.ArrangeOverride(finalSize);
        }
    }

這個時候MeasureOverride和ArrangeOverride什麼都沒有做,如果直接運行會是什麼樣子呢?

image

這個時候界面就是一片空白。添加的6個Border控制項沒有顯示。Mypanel的灰色背景也沒有顯示,說明Mypanel的size是0,沒有顯示出來。

下麵來實現MeasureOverride方法,遍歷每個子控制項並調用Measure方法。

public class MyPanel : Panel
    {
        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (FrameworkElement child in Children)
            {
                child.Measure(availableSize);
            }
            return availableSize;

        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            return base.ArrangeOverride(finalSize);
        }
    }

這個時候Mypanel的面板顯示出來了,背景顏色是灰色,但是子控制項沒有顯示。

image

接下來,實現ArrangeOverride方法

protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0;
            double y = 0;

            foreach (FrameworkElement child in Children)
            {
                child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
                x += child.DesiredSize.Width;
                y += child.DesiredSize.Height;

            }
            return finalSize;
        }

運行結果:

image

子控制項出來了,超出Page範圍的會被遮住。這個時候就可以根據需要定義每個子控制項的(x,y)坐標進行佈局了。如果要橫向佈局,就遞增x坐標,如果縱向佈局就遞增y坐標。

橫向佈局

遞增x坐標

protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0;
            double y = 0;

            foreach (FrameworkElement child in Children)
            {
                child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
                x += child.DesiredSize.Width;
               // y += child.DesiredSize.Height;

            }
            return finalSize;
        }

運行結果

image

縱向佈局

遞增y坐標

protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0;
            double y = 0;

            foreach (FrameworkElement child in Children)
            {
                child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
                //x += child.DesiredSize.Width;
                y += child.DesiredSize.Height;

            }
            return finalSize;
        }

運行結果:

image

接下來的問題,就是橫向或者縱向佈局的時候,判斷何時該換行了。要換行就要計算依次排列的子控制項的寬和高,同時和Mypanel的大小進行比較是否超出邊界。

這其中還有個問題是MeasureOverrideArrangeOverride 都會返回一個size大小。這兩個大小有什麼不一樣嗎。

MeasureOverride 返回值:此對象在佈局過程中基於其對子對象分配大小的計算或者基於固定容器大小等其他因素而確定的它所需的大小。

ArrangeOverride 返回值:元素在佈局中排列後使用的實際大小。

MeasureOverride的輸入size和返回size

可以做幾個實驗看看實際效果:

1. 如果給Measure傳遞的值較小,例如比最小的子控制項還小:

protected override Size MeasureOverride(Size availableSize)
        {
            foreach (FrameworkElement child in Children)
            {
                child.Measure(new Size(50,50));
            }
            return availableSize;
        }

運行結果

image

子控制項會被裁剪部分區域,顯示不完整,以適應較小的size。

2. 如果給Measure傳遞的值較大,比子控制項的大小要大。

protected override Size MeasureOverride(Size availableSize)
        {
            foreach (FrameworkElement child in Children)
            {
                child.Measure(new Size(400,400));
            }
            return availableSize;
        }

運行結果

image

結果顯示還是正常的大小,沒有變化。

總結:

說明子控制項的DesiredSize的會受到Measure傳遞的大小的限制,過小就會被裁剪,過大,不受影響,以實際的DesiredSize顯示。Measure方法就是給控制項分配一個可以顯示的大小範圍。

下麵看看MeasureOverride返回值,實際上和Measure的作用是一樣的,給MeasureOverride方法的參數size是MyPanel的父控制項給MyPanel分配的顯示區域大小,實際上會受到MyPanel 的Width、Height、HorizontalAlignment、VerticalAlignment等設置的影響,這裡就不展開了。

如果子控制項的大小顯示區域超過了MyPanel的父控制項給MyPanel分配的顯示區域大小,子控制項的顯示區域會被裁剪。這個時候可以根據業務需要調整MeasureOverride 返回size或者調整每個子控制項的Measure輸入size,縮小子控制項,使每個子控制項都完整顯示出來。

下麵看看如果設置的MeasureOverride返回值過小是什麼效果

protected override Size MeasureOverride(Size availableSize)
        {
            foreach (FrameworkElement child in Children)
            {
                child.Measure(availableSize);
            }
            return new Size(150,150);
        }

運行結果:

image

灰色區域是容器的大小,各個子控制項已經超出容器控制項的大小範圍了,MyPanel的父控制項分配的大小是整個page的大小,在遍歷子控制項Border時分配給子控制項也是page的大小顯示區域,但是每個子控制項都設置了Width和Height,而child.Measure(availableSize)的大小比子控制項自己的size大,所以最後會顯示控制項實際的大小,不會受child.Measure(availableSize)的影響,但是MyPanel的MeasureOverride最後返回的時候,size被修改小了,這樣整個容器就顯示被修改後的大小,子控制項溢出邊界。

所以在自定義容器控制項的時候,MeasureOverride 方法返回的size應該是所有子控制項顯示區域的最小size。而計算顯示區域的最小size,應該根據子控制項的佈局方式來判斷。

ArrangeOverride的輸入size和返回size

需要註意的是:通常情況下ArrangeOverride輸入的size和返回的size一樣,如果返回的size過小,也會遇到和MeasureOverride返回的size過小一樣的顯示問題。所以不建議修改ArrangeOverride的返回size,直接將輸入size返回就可以了,ArrangeOverride方法主要作用是給子控制項定位坐標和大小,完成佈局。

有了以上的準備,應該就知道怎麼實現淘寶的業務了,主要是在水平方向上依次排列子控制項,然後自動換行。

首先在MeasureOverride裡面計運算元控制項需要顯示大小區域,在ArrangeOverride裡面根據子控制項的大小排列方式計算顯示坐標,實現自動換行。

protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0;
            double y = 0;
            double maxHeight = 0;

            foreach (FrameworkElement child in Children)
            {
                if (maxHeight < child.DesiredSize.Height)
                {
                    maxHeight = child.DesiredSize.Height;
                }

                if ((x + child.DesiredSize.Width) > finalSize.Width)
                {
                    x = 0;
                    y += maxHeight;
                    maxHeight = 0;
                }

                child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
                x += child.DesiredSize.Width;
            }
            return finalSize;
        }

運行結果:

image

 

如果要實現更複雜的功能,例如要同時支持可以橫向縱向排列子控制項,就需要做一些封裝了,這裡就不一一展開了,最後附上有完整功能的WrapPanel實現代碼供大家參考。可以實現橫向和縱向排列。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1:使用epplus合併多個excel文件到同一excel的不同sheet頁中 2:設置excel文件sheet頁的 頁邊距(使用epplus) ...
  • 來源:http://www.cnblogs.com/Wayou/archive/2012/09/20/EF_CodeFirst.html Entity Framework的全稱是ADO.NET Entity Framework,是微軟開發的基於ADO.NET的ORM(Object/Relationa ...
  • C#中的委托與游戲中的運用 1.什麼是委托 在C/C++中,有函數指針的概念,即指向函數的指針。當我們調用該指針時,就相當於調用了該指針所指向的函數,這就是函數指針的一個作用,而他另一個作用就是將自己作為其他函數的參數。 但是指針是直接訪問記憶體單元的,程式員對指針的不恰當使用常常會引發錯誤。因此作為 ...
  • 不做開篇廢話,我們發現: AdaptiveTrigger 不夠好 我們知道,UWP可以在一個頁面適應不同尺寸比例的屏幕。一般來說這個功能是通過官方推薦的AdaptiveTrigger 進行的。 比如這樣: 我們可以看到這樣的的Trigger制定了最小值,隱含了條件“當滿足長寬都大於於這個條件時,這個 ...
  • 1、web前臺頁面文件上傳的文件大小限制問題 1.1 通過Web.Config配置上傳文件的大小限制(涉及站點安全問題,謹慎配置!!!)。 1.2 通過後臺C#代碼進行上傳文件大小的限制。 上述兩種方法的使用可滿足不同的需求,並不是可以相互替代的解決方法。新手總結,還望讀者自行甄別使用。 2、.ne ...
  • 本博客不屬於技術貼,主要是記錄一些自己對不懂得地方的理解和學習的記錄,請帶著批判的眼光閱讀~ 值類型存儲在棧上,引用類型存儲在堆上。棧是由高到低存儲的,遵循先進後出的原則,是記憶體提前分配好的區域,記憶體的釋放不由人控制,用完後就會釋放記憶體。堆是動態分配的記憶體區域,是無序的,受人為控制釋放,但是達到一定 ...
  • MD5是一個安全的散列演算法,輸入兩個不同的明文不會得到相同的輸出值,根據輸出值,不能得到原始的明文,即其過程不可逆;所以要解密MD5沒有現成的演算法,只能用窮舉法,把可能出現的明文,用MD5演算法散列之後,把得到的散列值和原始的數據形成一個一對一的映射表,通過比在表中比破解密碼的MD5演算法散列值,通過匹 ...
  • 在做一個屬性入庫的功能,將Excel屬性數據導入到圖層要素當中,這裡Excel和SDE資料庫數據存在一個關聯欄位,通過關聯欄位值進行匹配屬性入庫。 在實際業務中,由於普查數據往往某些欄位值比較複雜,在寫入到圖層中時用戶可能做一些簡化,例如一個要素編號為0532BH001,可能錄入到圖層中只錄入BH0... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...