WrapPanel改进
2023-02-04改进
存在一定程度的除以0导致布局异常
2023-02-02
WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
目的:改进下WrapPanel, 多实现一个空间超出时候的充满效果,
可以参考VS的Team中git面板的那个,实现一个 类似git提交代码 最后一个item充满整行的效果
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
namespace WrapPanelFillDemo
{
/// <summary>
/// 2023-02-02
/// WrapPanel改进后,增加了填充式布局,转载请保留博客地址!
/// 来源:https://www.cnblogs.com/wandia/p/17084881.html
/// 作者:陈-林-赵-魏
/// WrapPanel改进,增加了设置元素宽度高度时候填充式铺满
/// 代码改自 Microsoft WrapPanel 源码
/// </summary>
public class WrapPanelFill : WrapPanel
{
#region 依赖属性 是否填充式布局
public bool IsAdaptiveLayout
{
get { return (bool)GetValue(IsAdaptiveLayoutProperty); }
set { SetValue(IsAdaptiveLayoutProperty, value); }
}
public static readonly DependencyProperty IsAdaptiveLayoutProperty =
DependencyProperty.Register("IsAdaptiveLayout", typeof(bool), typeof(WrapPanelFill), new UIPropertyMetadata(false, OnIsAdaptiveLayout_ProperthChanged));
private static void OnIsAdaptiveLayout_ProperthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
WrapPanelFill fill = d as WrapPanelFill;
if (fill != null) fill.InvalidateMeasure();
}
#endregion
#region 浮点数比较静态方法
private const double DBL_EPSILON = 2.2204460492503131e-016; /* smallest such that 1.0+DBL_EPSILON != 1.0 */
private static bool DoubleAreClose(double value1, double value2)
{
//in case they are Infinities (then epsilon check does not work)
if (value1 == value2)
{
return true;
}
// This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DBL_EPSILON
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
double delta = value1 - value2;
return -eps < delta && eps > delta;
}
private static bool DoubleGreaterThan(double value1, double value2)
{
return value1 > value2 && !DoubleAreClose(value1, value2);
}
#endregion
#region 是否设置元素宽度高度
private bool IsItemWidthSet { get { return !double.IsNaN(this.ItemWidth) && this.ItemWidth > 0; } }
private bool IsItemHeightSet { get { return !double.IsNaN(this.ItemHeight) && this.ItemHeight > 0; } }
#endregion
#region MeasureOverride
/// <summary>
/// <see cref="FrameworkElement.MeasureOverride" />
/// </summary>
protected override Size MeasureOverride(Size constraint)
{
UVSize uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
UVSize curLineSize = new UVSize(Orientation);
UVSize panelSize = new UVSize(Orientation);
Size childConstraint = GetConstraintSize(constraint);
UIElementCollection children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
{
UIElement child = children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
UVSize sz = new UVSize(
Orientation,
(IsItemWidthSet ? childConstraint.Width : child.DesiredSize.Width),
(IsItemHeightSet ? childConstraint.Height : child.DesiredSize.Height));
if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
{ //need to switch to another line
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (DoubleGreaterThan(sz.U, uvConstraint.U))
{ //the element is wider then the constrint - give it a separate line
panelSize.U = Math.Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(Orientation); //用于存放全新1行狂赌
}
}
else
{//continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//the last line size, if any should be added
panelSize.U = Math.Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
//得到元素测量时所用宽度
private Size GetConstraintSize(Size constraint)
{
int visibleCount = this.InternalChildren.Cast<UIElement>().Count(p => p != null && p.Visibility != Visibility.Collapsed);
Size childConstraint = new Size(constraint.Width, constraint.Height);
if (IsItemWidthSet == true)
{
if (Orientation == Orientation.Horizontal && !double.IsInfinity(constraint.Width) && this.IsAdaptiveLayout == true)
{
var count = Math.Floor(constraint.Width / this.ItemWidth); //元素个数
if (visibleCount >= count)
{
var averageWidth = constraint.Width / Math.Max(count, 1); //平均宽度
childConstraint.Width = averageWidth;
}
else if (visibleCount > 0)
{
childConstraint.Width = constraint.Width / visibleCount;
}
}
else
{
childConstraint.Width = this.ItemWidth;
}
}
if (IsItemHeightSet == true)
{
if (Orientation == Orientation.Vertical && !double.IsInfinity(constraint.Height) && this.IsAdaptiveLayout == true)
{
var count = Math.Floor(constraint.Height / this.ItemHeight); //元素个数
if (visibleCount >= count)
{
var averageHeight = constraint.Height / Math.Max(count, 1); //平均宽度
childConstraint.Height = averageHeight;
}
else if (visibleCount > 0)
{
childConstraint.Height = constraint.Height / visibleCount;
}
}
else
{
childConstraint.Height = this.ItemHeight;
}
}
return childConstraint;
}
#endregion
#region ArrangeOverride
/// <summary>
/// <see cref="FrameworkElement.ArrangeOverride" />
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
int firstInLine = 0;
double accumulatedV = 0;
UVSize uvConstraint = new UVSize(Orientation, finalSize.Width, finalSize.Height);
UVSize curLineSize = new UVSize(Orientation);
Size childConstraint = GetConstraintSize(finalSize);
bool useItemU = (Orientation == Orientation.Horizontal ? IsItemWidthSet : IsItemHeightSet);
double itemU = (Orientation == Orientation.Horizontal ? childConstraint.Width : childConstraint.Height);
UIElementCollection children = InternalChildren;
for (int i = 0, count = children.Count; i < count; i++)
{
UIElement child = children[i];
if (child == null) continue;
UVSize sz = new UVSize(
Orientation,
(IsItemWidthSet ? childConstraint.Width : child.DesiredSize.Width),
(IsItemHeightSet ? childConstraint.Height : child.DesiredSize.Height));
if (DoubleGreaterThan(curLineSize.U + sz.U, uvConstraint.U))
{ //need to switch to another line
arrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
accumulatedV += curLineSize.V;
curLineSize = sz;
if (DoubleGreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constraint - give it a separate line
{
//switch to next line which only contain one element
arrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
}
firstInLine = i;
}
else
{ //continue to accumulate a line
curLineSize.U += sz.U;
curLineSize.V = Math.Max(sz.V, curLineSize.V);
}
}
//arrange the last line, if any
if (firstInLine < children.Count)
{
arrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
}
return finalSize;
}
private void arrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
{
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
if (child != null)
{
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = (useItemU ? itemU : childSize.U);
child.Arrange(new Rect(
(isHorizontal ? u : v),
(isHorizontal ? v : u),
(isHorizontal ? layoutSlotU : lineV),
(isHorizontal ? lineV : layoutSlotU)));
u += layoutSlotU;
}
}
}
#endregion
#region Private Struct
private struct UVSize
{
internal UVSize(Orientation orientation, double width, double height)
{
U = V = 0d;
_orientation = orientation;
Width = width;
Height = height;
}
internal UVSize(Orientation orientation)
{
U = V = 0d;
_orientation = orientation;
}
internal double U;
internal double V;
private Orientation _orientation;
internal double Width
{
get { return (_orientation == Orientation.Horizontal ? U : V); }
set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
internal double Height
{
get { return (_orientation == Orientation.Horizontal ? V : U); }
set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}
#endregion
}
}
运用:
<Window x:Class="WrapPanelFillDemo.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:WrapPanelFillDemo"
mc:Ignorable="d"
Title="Team Explorer-Home" Height="414" Width="324">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold" Text="元素宽度高度设置时 填充式 WrapPanel(陈-林-赵-魏)" Margin="0,5"/>
<WrapPanel Grid.Row="1" Margin="0,2">
<CheckBox x:Name="ChkAdaptiveLayout" IsChecked="True" Content="填充式布局"
VerticalAlignment="Center" VerticalContentAlignment="Center"
Margin="0,0,50,0"/>
<TextBlock Text="元素宽度" VerticalAlignment="Center"/>
<TextBox x:Name="txtItemWidth" MinWidth="120" Height="22" VerticalContentAlignment="Center"
Text="{Binding ElementName=WrapPanelFill,Path=ItemWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</WrapPanel>
<local:WrapPanelFill x:Name="WrapPanelFill" IsAdaptiveLayout="{Binding ElementName=ChkAdaptiveLayout,Path=IsChecked,Mode=TwoWay}" ItemWidth="150" Grid.Row="2">
<DockPanel Height="35" Margin="0,0,5,2">
<DockPanel.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFA6A6A6" Offset="0"/>
<GradientStop Color="#FFCFCFCF" Offset="1"/>
</LinearGradientBrush>
</DockPanel.Background>
<Rectangle Fill="#F05033" Width="4"/>
<Grid Margin="5">
<Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
<Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB8B2B2" />
</Grid>
<TextBlock Text="Changed" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
</DockPanel>
<DockPanel Height="35" Margin="0,0,5,2">
<DockPanel.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFA6A6A6" Offset="0"/>
<GradientStop Color="#FFCFCFCF" Offset="1"/>
</LinearGradientBrush>
</DockPanel.Background>
<Rectangle Fill="#F05033" Width="4"/>
<Grid Margin="5" >
<Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
<Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFDCA1A1" />
</Grid>
<TextBlock Text="Branches" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
</DockPanel>
<DockPanel Height="35" Margin="0,0,5,2">
<DockPanel.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFA6A6A6" Offset="0"/>
<GradientStop Color="#FFCFCFCF" Offset="1"/>
</LinearGradientBrush>
</DockPanel.Background>
<Rectangle Fill="#FF3333F0" Width="4"/>
<Grid Margin="5" >
<Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
<Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FFB5B9BD" />
</Grid>
<TextBlock Text="Sync" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
</DockPanel>
<DockPanel Height="35" Margin="0,0,5,2">
<DockPanel.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFA6A6A6" Offset="0"/>
<GradientStop Color="#FFCFCFCF" Offset="1"/>
</LinearGradientBrush>
</DockPanel.Background>
<Rectangle Fill="#FF616161" Width="4"/>
<Grid Margin="5" >
<Ellipse StrokeThickness="2" Width="25" Height="25" Stroke="Black"/>
<Ellipse StrokeThickness="2" Width="17" Height="17" Fill="#FF3E3E3E" />
</Grid>
<TextBlock Text="Setting" FontWeight="Medium" FontSize="16" VerticalAlignment="Center"/>
</DockPanel>
</local:WrapPanelFill>
<Border Grid.Row="3" BorderThickness="1" BorderBrush="Red" Margin="0,4">
<Label Content="这里是VS扩展git源码提交部分界面不实现!"/>
</Border>
</Grid>
</Window>
运行结果
转载保留源出处即可,商业使用请自行鉴别,使用本博客中公开内容做任何违法犯罪于本作者无关