【自定义控件】WrapBeakPanel-自定义代有换行功能的Panel

自定义代有换行功能的Panel

WrapBreakPanel.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace MyPanels
{
    public class WrapBreakPanel:Panel
    {
        
         
       
        public static readonly DependencyProperty LineBreakBeforeProperty;

        static WrapBreakPanel()
        {
            FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
            metadata.AffectsArrange = true;
            metadata.AffectsMeasure = true;


            LineBreakBeforeProperty =
                 DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), metadata);
        } 


 
        public static Boolean GetLineBreakBefore(UIElement element)
        {
            return (bool)element.GetValue(LineBreakBeforeProperty);
      
        }

        public static void SetLineBreakBefore(UIElement element, Boolean value)
        {
            element.SetValue(LineBreakBeforeProperty, value);
          
        }


        /// <summary>
        /// 测量阶段 
        /// 测量WrapBreakPanel 所要的期望大小(height,width)
        /// Desired Size ≤ Actual Size ≤ Available Size
        /// Desired Size:期望尺寸是子元素想要的尺寸; 对整个UI页面的检测,并询问每个元素的期望尺寸
        /// Actual Size:实际尺寸是父元元素分配给子元素的最终尺寸。
        /// Avilable Size:可用尺寸是测量阶段的初始约束值,即父元素愿意给子元素的最大空间值;
        /// 
        /// </summary>
        /// <param name="availableSize">可用大小(height,width)</param>
        /// <returns>所要的期望大小(height,width)</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
          

            Size currentLineSize = new Size();
            Size panelSize = new Size();
            foreach (UIElement element in base.InternalChildren)
            {
                //1、传入可以用大小,计算完后得到元素的期望值 询问孩子:你想要多大的空间显示自己?
                element.Measure(availableSize);

                ///2、获取元素的期望大小 孩子接到询问后,根据父给的availableSize以及自己的一些限制,比如Margin,Width,等等,
                ///孩子回答:我想要XXX大小的空间。
                Size desiredSize = element.DesiredSize;

               
                //3、父拿到孩子给的期望的空间大小后,根据自己的策略开始真正给孩子分配空间,就进入第二个子过程。
                //判断此元素是否是 断点元素
                if (GetLineBreakBefore(element) || currentLineSize.Width + desiredSize.Width > availableSize.Width)
                {
                    //切换到新行(要么是因为元素请求了它,要么是因为空间用完了)。
                    panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
                    panelSize.Height += currentLineSize.Height;
                    currentLineSize = desiredSize;

                }
                else
                {
                    //继续添加到当前行。
                    currentLineSize.Width += desiredSize.Width;

                    //确保该行与它的最高元素一样高。
                    currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);

                   

                }
            }
                panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
                panelSize.Height += currentLineSize.Height;
                return panelSize;
 

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="arrangeBounds"></param>
        /// <returns>实即大小</returns>
        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;

                    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;
            }
        }



    }
}

xaml.cs

<Window x:Class="WrapBreakPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 
        xmlns:local="clr-namespace:WrapBreakPanel" xmlns:my="clr-namespace:MyPanels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <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>
            <my:WrapBreakPanel>
                <Button  >No1 Break Here</Button>
                <Button my:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold">No2 Break Here</Button>
                <Button>No3 Break Here</Button>
                <Button>No4 Break Here</Button>
                <Button my:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold">Button with Break</Button>
                <Button>No4 Break Here</Button>
                <Button>No5 Break Here</Button>
                <Button>No6 Break Here</Button>
                <Button>No7 Break Here</Button>
            </my:WrapBreakPanel>
            <TextBlock Padding="5" Background="LightGray">Content below the WrapBreakPanel.</TextBlock>
        </StackPanel>
    </Grid>
</Window>

 

效果

 

posted @ 2022-11-28 06:28  小林野夫  阅读(52)  评论(0编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/