From 30e7017defa94d5dd5789d57308032c78e1de1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=B9=E5=BF=97=E8=8A=B3?= Date: Wed, 22 Oct 2025 11:56:39 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=EF=BC=9ARow=E9=94=99=E8=AF=AF=E8=A1=8C?= =?UTF-8?q?=E6=95=B0=E3=80=81=E9=AB=98=E5=BA=A6=E5=AF=BC=E8=87=B4=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E5=BC=8F=E5=B8=83=E5=B1=80=E4=B8=8D=E7=94=9F=E6=95=88?= =?UTF-8?q?=EF=BC=8C=E5=85=B7=E4=BD=93=E8=A1=A8=E7=8E=B0=E4=B8=BA=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=E6=B8=B2=E6=9F=93=E6=97=B6=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E9=AB=98=E5=BA=A6=E8=AE=A1=E7=AE=97=E9=94=99=E8=AF=AF=EF=BC=8C?= =?UTF-8?q?=E9=BC=A0=E6=A0=87=E7=A7=BB=E5=85=A5=E6=8E=A7=E4=BB=B6=E6=97=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E5=B8=83=E5=B1=80=E6=9B=B4=E6=96=B0=EF=BC=8C?= =?UTF-8?q?=E9=AB=98=E5=BA=A6=E6=89=8D=E6=AD=A3=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/Panel/Grid/Row.cs | 94 +++++++++++++++++-- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs b/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs index 3b4bd3ce4..41bed2613 100644 --- a/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs +++ b/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Threading; using HandyControl.Data; using HandyControl.Tools; using HandyControl.Tools.Extension; @@ -30,18 +31,89 @@ public double Gutter set => SetValue(GutterProperty, value); } + // 当视觉父发生变化,订阅父的 SizeChanged / LayoutUpdated,确保在父确定尺寸后重新测量 + protected override void OnVisualParentChanged(DependencyObject oldParent) + { + base.OnVisualParentChanged(oldParent); + UnsubscribeParentSizeChanged(oldParent as FrameworkElement); + + if (VisualParent is FrameworkElement newParent) + { + // 如果父已经有具体宽度,可以立即 InvalidateMeasure + if (newParent.ActualWidth > 0) + { + InvalidateMeasure(); + } + else + { + // 等待父首次布局完成 + newParent.SizeChanged += Parent_SizeChanged; + newParent.LayoutUpdated += Parent_LayoutUpdated; + } + } + } + + private void UnsubscribeParentSizeChanged(FrameworkElement parent) + { + if (parent == null) return; + parent.SizeChanged -= Parent_SizeChanged; + parent.LayoutUpdated -= Parent_LayoutUpdated; + } + + private void Parent_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (sender is FrameworkElement parent && parent.ActualWidth > 0) + { + parent.SizeChanged -= Parent_SizeChanged; + parent.LayoutUpdated -= Parent_LayoutUpdated; + // 延迟到渲染优先级再触发重测,确保父已稳定 + Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); + } + } + + private void Parent_LayoutUpdated(object sender, EventArgs e) + { + if (sender is FrameworkElement parent && parent.ActualWidth > 0) + { + parent.SizeChanged -= Parent_SizeChanged; + parent.LayoutUpdated -= Parent_LayoutUpdated; + Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); + } + } + protected override Size MeasureOverride(Size constraint) { var gutter = Gutter; + + // 如果父没有给出有限宽度,尝试用父 ActualWidth + if (double.IsInfinity(constraint.Width) || constraint.Width == 0) + { + if (Parent is FrameworkElement parent && parent.ActualWidth > 0) + { + constraint = new Size(parent.ActualWidth, constraint.Height); + } + else + { + // 延迟再测量一帧,避免返回错误测量尺寸被上层缓存 + Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); + // 返回一个合理的最小高度(尽量不要返回 0 宽,否则会导致上层布局认为没有宽度) + return new Size(constraint.Width, 0); + } + } + + // 在 Measure 阶段应该自己根据 constraint 计算 layoutStatus,而不要依赖 _layoutStatus(它在 Arrange 中被设置) + var localLayoutStatus = ColLayout.GetLayoutStatus(constraint.Width); + var totalCellCount = 0; var totalRowCount = 1; _fixedWidth = 0; _maxChildDesiredHeight = 0; var cols = InternalChildren.OfType().ToList(); + // 先测量固定或不参与栅格的子元素,累加 fixed 宽度与最大高度 foreach (var child in cols) { - var cellCount = child.GetLayoutCellCount(_layoutStatus); + var cellCount = child.GetLayoutCellCount(localLayoutStatus); if (cellCount == 0 || child.IsFixed) { child.Measure(constraint); @@ -50,18 +122,19 @@ protected override Size MeasureOverride(Size constraint) } } - var itemWidth = (constraint.Width - _fixedWidth + gutter) / ColLayout.ColMaxCellCount; + var availableWidth = Math.Max(0, constraint.Width - _fixedWidth + gutter); + var itemWidth = availableWidth / ColLayout.ColMaxCellCount; itemWidth = Math.Max(0, itemWidth); foreach (var child in cols) { - var cellCount = child.GetLayoutCellCount(_layoutStatus); + var cellCount = child.GetLayoutCellCount(localLayoutStatus); if (cellCount > 0 && !child.IsFixed) { totalCellCount += cellCount; - var availableWidth = Math.Max(0, cellCount * itemWidth - gutter); + var availableChildWidth = Math.Max(0, cellCount * itemWidth - gutter); - child.Measure(new Size(availableWidth, constraint.Height)); + child.Measure(new Size(availableChildWidth, constraint.Height)); _maxChildDesiredHeight = Math.Max(_maxChildDesiredHeight, child.DesiredSize.Height); if (totalCellCount > ColLayout.ColMaxCellCount) @@ -72,7 +145,9 @@ protected override Size MeasureOverride(Size constraint) } } - return new Size(0, _maxChildDesiredHeight * totalRowCount); + // 返回宽度尽量用 constraint.Width(如果是 Infinity 则尝试 parent.ActualWidth),避免返回 0 宽度 + var returnWidth = double.IsInfinity(constraint.Width) ? (Parent is FrameworkElement p ? p.ActualWidth : 0) : constraint.Width; + return new Size(returnWidth, _maxChildDesiredHeight * totalRowCount); } protected override Size ArrangeOverride(Size finalSize) @@ -80,11 +155,14 @@ protected override Size ArrangeOverride(Size finalSize) var gutter = Gutter; var totalCellCount = 0; var cols = InternalChildren.OfType().ToList(); + + // 再次设置布局状态,供 Arrange 使用 + _layoutStatus = ColLayout.GetLayoutStatus(finalSize.Width); + var itemWidth = (finalSize.Width - _fixedWidth + gutter) / ColLayout.ColMaxCellCount; itemWidth = Math.Max(0, itemWidth); var childBounds = new Rect(0, 0, 0, _maxChildDesiredHeight); - _layoutStatus = ColLayout.GetLayoutStatus(finalSize.Width); foreach (var child in cols) { From b02bdec668d9fde472d7c72f546e0d7031f5236b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=B9=E5=BF=97=E8=8A=B3?= Date: Thu, 23 Oct 2025 11:35:24 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix=EF=BC=9A=E8=A7=A3=E5=86=B3Row=E3=80=81C?= =?UTF-8?q?ol=E5=93=8D=E5=BA=94=E5=BC=8F=E5=B8=83=E5=B1=80=EF=BC=9A?= =?UTF-8?q?=E5=85=83=E7=B4=A0=20=E2=80=9CHandyControl.Controls.Row?= =?UTF-8?q?=E2=80=9D=20=E7=9A=84=E5=B8=83=E5=B1=80=E6=B5=8B=E9=87=8F?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E4=B8=8D=E5=BA=94=E5=B0=86=20PositiveInfinit?= =?UTF-8?q?y=20=E4=BD=9C=E4=B8=BA=E5=85=B6=20DesiredSize=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=EF=BC=8C=E5=8D=B3=E4=BD=BF=E5=B0=86=20Infinity=20?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E5=8F=AF=E7=94=A8=E5=A4=A7=E5=B0=8F=E4=BC=A0?= =?UTF-8?q?=E5=85=A5=E3=80=82=E8=AF=B7=E5=9F=BA=E4=BA=8ERow=E7=9B=AE?= =?UTF-8?q?=E5=89=8D=E6=9C=89=E7=9A=84=E4=BB=A3=E7=A0=81=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E8=BF=99=E4=B8=AA=E5=91=8A=E8=AD=A6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controls/Panel/Grid/Row.cs | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs b/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs index 41bed2613..634abcb14 100644 --- a/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs +++ b/src/Shared/HandyControl_Shared/Controls/Panel/Grid/Row.cs @@ -12,9 +12,7 @@ namespace HandyControl.Controls; public class Row : Panel { private ColLayoutStatus _layoutStatus; - private double _maxChildDesiredHeight; - private double _fixedWidth; public static readonly DependencyProperty GutterProperty = DependencyProperty.Register( @@ -31,6 +29,7 @@ public double Gutter set => SetValue(GutterProperty, value); } + #region 父布局监控 // 当视觉父发生变化,订阅父的 SizeChanged / LayoutUpdated,确保在父确定尺寸后重新测量 protected override void OnVisualParentChanged(DependencyObject oldParent) { @@ -64,9 +63,7 @@ private void Parent_SizeChanged(object sender, SizeChangedEventArgs e) { if (sender is FrameworkElement parent && parent.ActualWidth > 0) { - parent.SizeChanged -= Parent_SizeChanged; - parent.LayoutUpdated -= Parent_LayoutUpdated; - // 延迟到渲染优先级再触发重测,确保父已稳定 + UnsubscribeParentSizeChanged(parent); Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); } } @@ -75,18 +72,19 @@ private void Parent_LayoutUpdated(object sender, EventArgs e) { if (sender is FrameworkElement parent && parent.ActualWidth > 0) { - parent.SizeChanged -= Parent_SizeChanged; - parent.LayoutUpdated -= Parent_LayoutUpdated; + UnsubscribeParentSizeChanged(parent); Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); } } + #endregion + protected override Size MeasureOverride(Size constraint) { var gutter = Gutter; - // 如果父没有给出有限宽度,尝试用父 ActualWidth - if (double.IsInfinity(constraint.Width) || constraint.Width == 0) + // --- 防止无限约束 --- + if (double.IsInfinity(constraint.Width) || double.IsNaN(constraint.Width) || constraint.Width <= 0) { if (Parent is FrameworkElement parent && parent.ActualWidth > 0) { @@ -94,14 +92,17 @@ protected override Size MeasureOverride(Size constraint) } else { - // 延迟再测量一帧,避免返回错误测量尺寸被上层缓存 + // 给出一个合理的默认宽度,避免返回 Infinity + constraint = new Size(100, constraint.Height); + // 延迟重新测量,等待父尺寸稳定 Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); - // 返回一个合理的最小高度(尽量不要返回 0 宽,否则会导致上层布局认为没有宽度) - return new Size(constraint.Width, 0); } } - // 在 Measure 阶段应该自己根据 constraint 计算 layoutStatus,而不要依赖 _layoutStatus(它在 Arrange 中被设置) + // 高度也进行防御 + if (double.IsInfinity(constraint.Height) || double.IsNaN(constraint.Height)) + constraint = new Size(constraint.Width, 100); + var localLayoutStatus = ColLayout.GetLayoutStatus(constraint.Width); var totalCellCount = 0; @@ -110,7 +111,7 @@ protected override Size MeasureOverride(Size constraint) _maxChildDesiredHeight = 0; var cols = InternalChildren.OfType().ToList(); - // 先测量固定或不参与栅格的子元素,累加 fixed 宽度与最大高度 + // 先测量固定或不参与栅格的子元素 foreach (var child in cols) { var cellCount = child.GetLayoutCellCount(localLayoutStatus); @@ -124,7 +125,6 @@ protected override Size MeasureOverride(Size constraint) var availableWidth = Math.Max(0, constraint.Width - _fixedWidth + gutter); var itemWidth = availableWidth / ColLayout.ColMaxCellCount; - itemWidth = Math.Max(0, itemWidth); foreach (var child in cols) { @@ -145,9 +145,22 @@ protected override Size MeasureOverride(Size constraint) } } - // 返回宽度尽量用 constraint.Width(如果是 Infinity 则尝试 parent.ActualWidth),避免返回 0 宽度 - var returnWidth = double.IsInfinity(constraint.Width) ? (Parent is FrameworkElement p ? p.ActualWidth : 0) : constraint.Width; - return new Size(returnWidth, _maxChildDesiredHeight * totalRowCount); + // --- 返回安全尺寸 --- + var returnWidth = constraint.Width; + var returnHeight = _maxChildDesiredHeight * totalRowCount; + + if (double.IsInfinity(returnWidth) || double.IsNaN(returnWidth) || returnWidth <= 0) + { + if (Parent is FrameworkElement p && p.ActualWidth > 0) + returnWidth = p.ActualWidth; + else + returnWidth = 100; // fallback + } + + if (double.IsInfinity(returnHeight) || double.IsNaN(returnHeight) || returnHeight < 0) + returnHeight = 0; + + return new Size(returnWidth, returnHeight); } protected override Size ArrangeOverride(Size finalSize) @@ -155,7 +168,6 @@ protected override Size ArrangeOverride(Size finalSize) var gutter = Gutter; var totalCellCount = 0; var cols = InternalChildren.OfType().ToList(); - // 再次设置布局状态,供 Arrange 使用 _layoutStatus = ColLayout.GetLayoutStatus(finalSize.Width); @@ -167,14 +179,15 @@ protected override Size ArrangeOverride(Size finalSize) foreach (var child in cols) { if (!child.IsVisible) - { continue; - } var cellCount = child.GetLayoutCellCount(_layoutStatus); totalCellCount += cellCount; - var childWidth = (cellCount > 0 && !child.IsFixed) ? Math.Max(0, cellCount * itemWidth - gutter) : child.DesiredSize.Width; + var childWidth = (cellCount > 0 && !child.IsFixed) + ? Math.Max(0, cellCount * itemWidth - gutter) + : child.DesiredSize.Width; + childBounds.Width = childWidth; childBounds.X += child.Offset * itemWidth;