using System;
using System.Collections.Generic;
using System.Text;
using CPF.Drawing;
using System.Runtime.CompilerServices;
using System.Linq;
using System.ComponentModel;
namespace CPF.Controls
{
///
/// 网格布局
///
[Description("网格布局")]
public class Grid : Panel
{
Collection columnDefinitions = new Collection();
Collection rowDefinitions = new Collection();
///
/// 网格布局
///
public Grid()
{
columnDefinitions.CollectionChanged += ColumnDefinitions_CollectionChanged;
//columnDefinitions.ItemRemoved += ColumnDefinitions_ItemRemoved;
rowDefinitions.CollectionChanged += RowDefinitions_CollectionChanged;
//rowDefinitions.ItemRemoved += RowDefinitions_ItemRemoved;
}
private void RowDefinitions_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
switch (e.Action)
{
case CollectionChangedAction.Add:
e.NewItem.PropertyChanged += Item_PropertyChanged;
e.NewItem[nameof(DataContext)] = new (this, nameof(DataContext));
e.NewItem[nameof(CommandContext)] = new (this, nameof(CommandContext));
break;
case CollectionChangedAction.Remove:
e.OldItem.PropertyChanged -= Item_PropertyChanged;
break;
case CollectionChangedAction.Replace:
e.NewItem.PropertyChanged += Item_PropertyChanged;
e.NewItem[nameof(DataContext)] = new (this, nameof(DataContext));
e.NewItem[nameof(CommandContext)] = new (this, nameof(CommandContext));
e.OldItem.PropertyChanged -= Item_PropertyChanged;
break;
default:
break;
}
InvalidateMeasure();
}
private void ColumnDefinitions_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
switch (e.Action)
{
case CollectionChangedAction.Add:
e.NewItem.PropertyChanged += Item_PropertyChanged;
e.NewItem[nameof(DataContext)] = new BindingDescribe(this, nameof(DataContext));
e.NewItem[nameof(CommandContext)] = new BindingDescribe(this, nameof(CommandContext));
break;
case CollectionChangedAction.Remove:
e.OldItem.PropertyChanged -= Item_PropertyChanged;
break;
case CollectionChangedAction.Replace:
e.NewItem.PropertyChanged += Item_PropertyChanged;
e.NewItem[nameof(DataContext)] = new BindingDescribe(this, nameof(DataContext));
e.NewItem[nameof(CommandContext)] = new BindingDescribe(this, nameof(CommandContext));
e.OldItem.PropertyChanged -= Item_PropertyChanged;
break;
}
InvalidateMeasure();
}
private void Item_PropertyChanged(object sender, CPFPropertyChangedEventArgs e)
{
InvalidateMeasure();
}
///
/// 列,在CSS里设置 ColumnDefinitions-索引-Width:'*'; 值包含*的情况下要加引号
///
[NotCpfProperty, Category("设计")]
public Collection ColumnDefinitions
{
get
{
return columnDefinitions;
}
}
///
/// 行,在CSS里设置 RowDefinitions-索引-Height:'*'; 值包含*的情况下要加引号
///
[NotCpfProperty, Category("设计")]
public Collection RowDefinitions
{
get
{
return rowDefinitions;
}
}
protected override Size MeasureOverride(in Size availableSize)
{
if (rowDefinitions.Count == 0 && columnDefinitions.Count == 0)
{
return base.MeasureOverride(availableSize);
}
else
{
if (rowDefinitions.Count == 0)
{
rowDefinitions.Add(new RowDefinition { });
}
if (columnDefinitions.Count == 0)
{
columnDefinitions.Add(new ColumnDefinition { });
}
var allWidth = 0f;
var allHeight = 0f;
var allWStar = 0f;
var allHStar = 0f;
//先计算绝对值的
for (int i = 0; i < columnDefinitions.Count; i++)
{
var item = columnDefinitions[i];
var w = item.Width;
if (w.IsAbsolute)
{
item.ActualWidth = w.Value;
if (!item.MaxWidth.IsAuto)
{
item.ActualWidth = Math.Min(item.ActualWidth, item.MaxWidth.GetActualValue(availableSize.Width));
}
if (!item.MinWidth.IsAuto)
{
item.ActualWidth = Math.Max(item.ActualWidth, item.MinWidth.GetActualValue(availableSize.Width));
}
}
else// if (w.IsAuto)
{
item.ActualWidth = 0;
}
if (w.IsStar)
{
allWStar += w.Value;
}
allWidth += item.ActualWidth;
if (item.UIElements == null)
{
item.UIElements = new List();
}
item.UIElements.Clear();
}
for (int i = 0; i < rowDefinitions.Count; i++)
{
var item = rowDefinitions[i];
var h = item.Height;
if (h.IsAbsolute)
{
item.ActualHeight = h.Value;
if (!item.MaxHeight.IsAuto)
{
item.ActualHeight = Math.Min(item.ActualHeight, item.MaxHeight.GetActualValue(availableSize.Height));
}
if (!item.MinHeight.IsAuto && item.MinHeight.Unit != Unit.Percent)
{
item.ActualHeight = Math.Max(item.ActualHeight, item.MinHeight.GetActualValue(availableSize.Height));
}
}
else// if (h.IsAuto)
{
item.ActualHeight = 0;
}
if (h.IsStar)
{
allHStar += h.Value;
}
allHeight += item.ActualHeight;
if (item.UIElements == null)
{
item.UIElements = new List();
}
item.UIElements.Clear();
}
//更新Star的尺寸
for (int i = 0; i < columnDefinitions.Count; i++)
{
var item = columnDefinitions[i];
var w = item.Width;
if (w.IsStar)
{
item.ActualWidth = Math.Max((availableSize.Width - allWidth) * (w.Value / allWStar), 0);
if (!item.MaxWidth.IsAuto)
{
item.ActualWidth = Math.Min(item.ActualWidth, item.MaxWidth.GetActualValue(availableSize.Width));
}
if (!item.MinWidth.IsAuto)
{
item.ActualWidth = Math.Max(item.ActualWidth, item.MinWidth.GetActualValue(availableSize.Width));
}
}
}
for (int i = 0; i < rowDefinitions.Count; i++)
{
var item = rowDefinitions[i];
var h = item.Height;
if (h.IsStar)
{
item.ActualHeight = Math.Max((availableSize.Height - allHeight) * (h.Value / allHStar), 0);
if (!item.MaxHeight.IsAuto)
{
item.ActualHeight = Math.Min(item.ActualHeight, item.MaxHeight.GetActualValue(availableSize.Height));
}
if (!item.MinHeight.IsAuto && item.MinHeight.Unit != Unit.Percent)
{
item.ActualHeight = Math.Max(item.ActualHeight, item.MinHeight.GetActualValue(availableSize.Height));
}
}
}
//先计算子元素尺寸,再计算自动尺寸的
for (int i = 0; i < Children.Count; i++)
{
var item = Children[i];
var child = Children[i];
var c = GetColumnIndex(child);
var r = GetRowIndex(child);
var col = columnDefinitions[c];
var row = rowDefinitions[r];
col.UIElements.Add(item);
row.UIElements.Add(item);
var spanCol = Math.Max(ColumnSpan(child), 1);
var spanRow = Math.Max(RowSpan(child), 1);
var w = col.ActualWidth;
var h = row.ActualHeight;
for (int j = c + 1; j < Math.Min(c + spanCol, columnDefinitions.Count); j++)
{
w += columnDefinitions[j].ActualWidth;
}
for (int j = r + 1; j < Math.Min(r + spanRow, rowDefinitions.Count); j++)
{
h += rowDefinitions[j].ActualHeight;
}
item.Measure(new Size(
col.Width.IsAuto ? float.PositiveInfinity : w,
row.Height.IsAuto ? float.PositiveInfinity : h));
}
//计算自动尺寸的
foreach (ColumnDefinition item in columnDefinitions)
{
var w = item.Width;
if (!w.IsAbsolute && item.UIElements.Count > 0)
{
var q = item.UIElements.Where(a => ColumnSpan(a) == 1);
if (q.Count() > 0)
{
item.ActualWidth = q.Max(a => a.DesiredSize.Width);
}
else
{
item.ActualWidth = 0;
}
}
else if (!w.IsAbsolute)
{
item.ActualWidth = 0;
}
if (!item.MaxWidth.IsAuto)
{
item.ActualWidth = Math.Min(item.ActualWidth, item.MaxWidth.GetActualValue(availableSize.Width));
}
if (!item.MinWidth.IsAuto)
{
item.ActualWidth = Math.Max(item.ActualWidth, item.MinWidth.GetActualValue(availableSize.Width));
}
}
foreach (RowDefinition item in rowDefinitions)
{
var h = item.Height;
if (!h.IsAbsolute && item.UIElements.Count > 0)
{
var q = item.UIElements.Where(a => RowSpan(a) == 1);
if (q.Count() > 0)
{
item.ActualHeight = q.Max(a => a.DesiredSize.Height);
}
else
{
item.ActualHeight = 0;
}
}
else if (!h.IsAbsolute)
{
item.ActualHeight = 0;
}
if (!item.MaxHeight.IsAuto)
{
item.ActualHeight = Math.Min(item.ActualHeight, item.MaxHeight.GetActualValue(availableSize.Height));
}
if (!item.MinHeight.IsAuto && item.MinHeight.Unit != Unit.Percent)
{
item.ActualHeight = Math.Max(item.ActualHeight, item.MinHeight.GetActualValue(availableSize.Height));
}
}
return new Size(columnDefinitions.Sum(a => a.ActualWidth), rowDefinitions.Sum(a => a.ActualHeight));
}
}
protected override Size ArrangeOverride(in Size finalSize)
{
if (rowDefinitions.Count == 0 && columnDefinitions.Count == 0)
{
return base.ArrangeOverride(finalSize);
}
else
{
var rect = new Rect(0, 0, finalSize.Width, finalSize.Height);
//var offsetX = 0f;
var allWidth = 0f;//除了star的总宽度
var allWStar = 0f;
//计算权重总和和验证最大化最小化
for (int i = 0; i < columnDefinitions.Count; i++)
{
var item = columnDefinitions[i];
var w = item.Width;
if (!w.IsStar)
{
if (!item.MaxWidth.IsAuto)
{
item.ActualWidth = Math.Min(item.ActualWidth, item.MaxWidth.GetActualValue(finalSize.Width));
}
if (!item.MinWidth.IsAuto)
{
item.ActualWidth = Math.Max(item.ActualWidth, item.MinWidth.GetActualValue(finalSize.Width));
}
allWidth += item.ActualWidth;
}
else
{
allWStar += w.Value;
}
}
for (int i = 0; i < columnDefinitions.Count; i++)
{
var item = columnDefinitions[i];
var w = item.Width;
if (w.IsStar)
{
item.ActualWidth = Math.Max(finalSize.Width - allWidth, 0) * (w.Value / allWStar);
if (!item.MaxWidth.IsAuto)
{
item.ActualWidth = Math.Min(item.ActualWidth, item.MaxWidth.GetActualValue(finalSize.Width));
}
if (!item.MinWidth.IsAuto)
{
item.ActualWidth = Math.Max(item.ActualWidth, item.MinWidth.GetActualValue(finalSize.Width));
}
}
}
var offsetX = 0f;//更新offset
for (int i = 0; i < columnDefinitions.Count; i++)
{
var item = columnDefinitions[i];
item.offset = offsetX;
offsetX += item.ActualWidth;
}
var allHeight = 0f;
var allHStar = 0f;
for (int i = 0; i < rowDefinitions.Count; i++)
{
var item = rowDefinitions[i];
var h = item.Height;
if (!h.IsStar)
{
if (!item.MaxHeight.IsAuto)
{
item.ActualHeight = Math.Min(item.ActualHeight, item.MaxHeight.GetActualValue(finalSize.Height));
}
if (!item.MinHeight.IsAuto && item.MinHeight.Unit != Unit.Percent)
{
item.ActualHeight = Math.Max(item.ActualHeight, item.MinHeight.GetActualValue(finalSize.Height));
}
allHeight += item.ActualHeight;
}
else
{
allHStar += h.Value;
}
}
for (int i = 0; i < rowDefinitions.Count; i++)
{
var item = rowDefinitions[i];
var h = item.Height;
if (h.IsStar)
{
item.ActualHeight = Math.Max(finalSize.Height - allHeight, 0) * (h.Value / allHStar);
if (!item.MaxHeight.IsAuto)
{
item.ActualHeight = Math.Min(item.ActualHeight, item.MaxHeight.GetActualValue(finalSize.Height));
}
if (!item.MinHeight.IsAuto)
{
item.ActualHeight = Math.Max(item.ActualHeight, item.MinHeight.GetActualValue(finalSize.Height));
}
}
}
var offsetY = 0f;
for (int i = 0; i < rowDefinitions.Count; i++)
{
var item = rowDefinitions[i];
item.offset = offsetY;
offsetY += item.ActualHeight;
}
for (int i = 0; i < Children.Count; i++)
{
var child = Children[i];
var c = GetColumnIndex(child);
var r = GetRowIndex(child);
var col = columnDefinitions[c];
var row = rowDefinitions[r];
var spanCol = Math.Max(ColumnSpan(child), 1);
var spanRow = Math.Max(RowSpan(child), 1);
var w = col.ActualWidth;
var h = row.ActualHeight;
for (int j = c + 1; j < Math.Min(c + spanCol, columnDefinitions.Count); j++)
{
w += columnDefinitions[j].ActualWidth;
}
for (int j = r + 1; j < Math.Min(r + spanRow, rowDefinitions.Count); j++)
{
h += rowDefinitions[j].ActualHeight;
}
child.Arrange(new Rect(col.offset, row.offset, w, h));
}
return finalSize;
}
}
int GetRowIndex(UIElement element)
{
var row = RowIndex(element);
if (row > rowDefinitions.Count - 1)
{
row = rowDefinitions.Count - 1;
}
else if (row < 0)
{
row = 0;
}
return row;
}
int GetColumnIndex(UIElement element)
{
var col = ColumnIndex(element);
if (col > columnDefinitions.Count - 1)
{
col = columnDefinitions.Count - 1;
}
else if (col < 0)
{
col = 0;
}
return col;
}
///
/// 获取或设置元素行索引
///
[Description("获取或设置元素行索引")]
public static Attached RowIndex
{
get
{
return RegisterAttached(0, typeof(Grid), (CpfObject obj, string propertyName, object defaultValue, object oldValue, ref object newValue) =>
{
if (obj is UIElement element && element.Parent != null)
{
element.Parent.InvalidateMeasure();
}
});
}
}
///
/// 获取或设置元素列索引
///
[Description("获取或设置元素列索引")]
public static Attached ColumnIndex
{
get
{
return RegisterAttached(0, typeof(Grid), (CpfObject obj, string propertyName, object defaultValue, object oldValue, ref object newValue) =>
{
if (obj is UIElement element && element.Parent != null)
{
element.Parent.InvalidateMeasure();
}
});
}
}
///
/// 获取或设置元素跨行
///
[Description("获取或设置元素跨行")]
public static Attached RowSpan
{
get
{
return RegisterAttached(1, typeof(Grid), (CpfObject obj, string propertyName, object defaultValue, object oldValue, ref object newValue) =>
{
if (obj is UIElement element && element.Parent != null)
{
element.Parent.InvalidateMeasure();
}
});
}
}
///
/// 获取或设置元素跨列
///
[Description("获取或设置元素跨列")]
public static Attached ColumnSpan
{
get
{
return RegisterAttached(1, typeof(Grid), (CpfObject obj, string propertyName, object defaultValue, object oldValue, ref object newValue) =>
{
if (obj is UIElement element && element.Parent != null)
{
element.Parent.InvalidateMeasure();
}
});
}
}
///
/// 网格线条填充
///
[Description("边框线条填充")]
[UIPropertyMetadata(null, UIPropertyOptions.AffectsRender)]
public ViewFill LineFill
{
get { return (ViewFill)GetValue(); }
set { SetValue(value); }
}
///
/// 获取或设置线条类型
///
[Description("获取或设置线条类型")]
[UIPropertyMetadata(typeof(Stroke), "0", UIPropertyOptions.AffectsRender)]
public Stroke LineStroke
{
get { return (Stroke)GetValue(); }
set { SetValue(value); }
}
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
var stroke = LineStroke;
var lineFill = LineFill;
var ac = ActualSize;
if (DesignMode && Site != null)
{
if (Site.GetType().GetProperty("ShowBorder") == null || (bool)Site.GetPropretyValue("ShowBorder"))
{
using (SolidColorBrush brush = new SolidColorBrush(Color.Gray))
{
Stroke stroke1 = new Stroke(1, DashStyles.DashDotDot);
for (int i = 1; i < columnDefinitions.Count; ++i)
{
DrawGridLine(
dc,
columnDefinitions[i].offset, 0f,
columnDefinitions[i].offset, ac.Height, stroke1, brush);
}
for (int i = 1; i < rowDefinitions.Count; ++i)
{
DrawGridLine(
dc,
0f, rowDefinitions[i].offset,
ac.Width, rowDefinitions[i].offset, stroke1, brush);
}
}
}
}
if (stroke.Width > 0 && lineFill != null)
{
using (var brush = lineFill.CreateBrush(new Rect(0, 0, ac.Width, ac.Height), Root.RenderScaling))
{
for (int i = 1; i < columnDefinitions.Count; ++i)
{
DrawGridLine(
dc,
columnDefinitions[i].offset, 0f,
columnDefinitions[i].offset, ac.Height, stroke, brush);
}
for (int i = 1; i < rowDefinitions.Count; ++i)
{
DrawGridLine(
dc,
0f, rowDefinitions[i].offset,
ac.Width, rowDefinitions[i].offset, stroke, brush);
}
}
}
}
private static void DrawGridLine(DrawingContext drawingContext, float startX, float startY, float endX, float endY, in Stroke stroke, in Brush brush)
{
Point start = new Point(startX, startY);
Point end = new Point(endX, endY);
//drawingContext.DrawLine(s_oddDashPen, start, end);
drawingContext.DrawLine(stroke, brush, start, end);
}
}
}