| File: System\Windows\Forms\Design\Behavior\TableLayoutPanelBehavior.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; namespace System.Windows.Forms.Design.Behavior; internal class TableLayoutPanelBehavior : Behavior { private readonly TableLayoutPanelDesigner _designer; // pointer back to our designer. private Point _lastMouseLoc; // used to track mouse movement deltas private bool _pushedBehavior; // tracks if we've pushed ourself onto the stack private readonly BehaviorService _behaviorService; // used for bounds translation private readonly IServiceProvider _serviceProvider; // cached to allow our behavior to get services private TableLayoutPanelResizeGlyph _tableGlyph; // the glyph being resized private DesignerTransaction _resizeTransaction; // used to make size adjustments within transaction private PropertyDescriptor _resizeProp; // cached property descriptor representing either the row or column styles private PropertyDescriptor _changedProp; // cached property descriptor that refers to the RowSTyles or ColumnStyles collection. private readonly TableLayoutPanel _table; private StyleHelper _rightStyle; private StyleHelper _leftStyle; private List<TableLayoutStyle> _styles; private bool _currentColumnStyles; // is Styles for Columns or Rows internal TableLayoutPanelBehavior(TableLayoutPanel panel, TableLayoutPanelDesigner designer, IServiceProvider serviceProvider) { _table = panel; _designer = designer; _serviceProvider = serviceProvider; _behaviorService = serviceProvider.GetService(typeof(BehaviorService)) as BehaviorService; if (_behaviorService is null) { Debug.Fail("BehaviorService could not be found!"); return; } _pushedBehavior = false; _lastMouseLoc = Point.Empty; } private void FinishResize() { // clear state _pushedBehavior = false; _behaviorService.PopBehavior(this); _lastMouseLoc = Point.Empty; _styles = null; // fire ComponentChange events so this event is undoable IComponentChangeService cs = _serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs is not null && _changedProp is not null) { cs.OnComponentChanged(_table, _changedProp, null, null); _changedProp = null; } // attempt to refresh the selection SelectionManager selManager = _serviceProvider.GetService(typeof(SelectionManager)) as SelectionManager; selManager?.Refresh(); } public override void OnLoseCapture(Glyph g, EventArgs e) { if (_pushedBehavior) { FinishResize(); // If we still have a transaction, roll it back. if (_resizeTransaction is not null) { DesignerTransaction t = _resizeTransaction; _resizeTransaction = null; using (t) { t.Cancel(); } } } } public override bool OnMouseDown(Glyph g, MouseButtons button, Point mouseLoc) { // we only care about the right mouse button for resizing if (button == MouseButtons.Left && g is TableLayoutPanelResizeGlyph) { _tableGlyph = g as TableLayoutPanelResizeGlyph; // select the table ISelectionService selSvc = _serviceProvider.GetService(typeof(ISelectionService)) as ISelectionService; selSvc?.SetSelectedComponents(new object[] { _designer.Component }, SelectionTypes.Primary); bool isColumn = _tableGlyph.Type == TableLayoutPanelResizeGlyph.TableLayoutResizeType.Column; // cache some state _lastMouseLoc = mouseLoc; _resizeProp = TypeDescriptor.GetProperties(_tableGlyph.Style)[isColumn ? "Width" : "Height"]; Debug.Assert(_resizeProp is not null, "Unable to get the resize property for tableGlyph's Style"); IComponentChangeService cs = _serviceProvider.GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs is not null) { _changedProp = TypeDescriptor.GetProperties(_table)[isColumn ? "ColumnStyles" : "RowStyles"]; int[] widths = isColumn ? _table.GetColumnWidths() : _table.GetRowHeights(); if (_changedProp is not null) { GetActiveStyleCollection(isColumn); if (_styles is not null && CanResizeStyle(widths)) { IDesignerHost host = _serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost; if (host is not null) { _resizeTransaction = host.CreateTransaction(string.Format(SR.TableLayoutPanelRowColResize, (isColumn ? "Column" : "Row"), _designer.Control.Site.Name)); } try { int moveIndex = _styles.IndexOf(_tableGlyph.Style); _rightStyle.index = IndexOfNextStealableStyle(true /*forward*/, moveIndex, widths); _rightStyle.style = _styles[_rightStyle.index]; _rightStyle.styleProp = TypeDescriptor.GetProperties(_rightStyle.style)[isColumn ? "Width" : "Height"]; _leftStyle.index = IndexOfNextStealableStyle(false /*backwards*/, moveIndex, widths); _leftStyle.style = _styles[_leftStyle.index]; _leftStyle.styleProp = TypeDescriptor.GetProperties(_leftStyle.style)[isColumn ? "Width" : "Height"]; Debug.Assert(_leftStyle.styleProp is not null && _rightStyle.styleProp is not null, "Couldn't find property descriptor for width or height"); cs.OnComponentChanging(_table, _changedProp); } catch (CheckoutException checkoutException) { if (CheckoutException.Canceled.Equals(checkoutException)) { if ((_resizeTransaction is not null) && (!_resizeTransaction.Canceled)) { _resizeTransaction.Cancel(); } } throw; } } else { return false; } } } // push this resizebehavior _behaviorService.PushCaptureBehavior(this); _pushedBehavior = true; } return false; } private void GetActiveStyleCollection(bool isColumn) { if ((_styles is null || isColumn != _currentColumnStyles) && _table is not null) { _styles = [..((TableLayoutStyleCollection)_changedProp.GetValue(_table)).Cast<TableLayoutStyle>()]; _currentColumnStyles = isColumn; } } private bool ColumnResize { get { bool ret = false; if (_tableGlyph is not null) { ret = _tableGlyph.Type == TableLayoutPanelResizeGlyph.TableLayoutResizeType.Column; } return ret; } } private bool CanResizeStyle(int[] widths) { int moveIndex = _styles.IndexOf(_tableGlyph.Style); if (moveIndex <= -1 || moveIndex == _styles.Count) { Debug.Fail($"Can't find style {moveIndex}"); return false; } bool canStealFromRight = IndexOfNextStealableStyle(true, moveIndex, widths) != -1; bool canStealFromLeft = IndexOfNextStealableStyle(false, moveIndex, widths) != -1; return canStealFromRight && canStealFromLeft; } private int IndexOfNextStealableStyle(bool forward, int startIndex, int[] widths) { int stealIndex = -1; if (_styles is not null) { if (forward) { for (int i = startIndex + 1; ((i < _styles.Count) && (i < widths.Length)); i++) { if (_styles[i].SizeType != SizeType.AutoSize && widths[i] >= DesignerUtils.s_minimumSizeDrag) { stealIndex = i; break; } } } else { if (startIndex < widths.Length) { for (int i = startIndex; i >= 0; i--) { if (_styles[i].SizeType != SizeType.AutoSize && widths[i] >= DesignerUtils.s_minimumSizeDrag) { stealIndex = i; break; } } } } } return stealIndex; } public override bool OnMouseMove(Glyph g, MouseButtons button, Point mouseLoc) { if (_pushedBehavior) { bool isColumn = ColumnResize; GetActiveStyleCollection(isColumn); if (_styles is not null) { int rightIndex = _rightStyle.index; int leftIndex = _leftStyle.index; int delta = isColumn ? mouseLoc.X - _lastMouseLoc.X : mouseLoc.Y - _lastMouseLoc.Y; if (isColumn && _table.RightToLeft == RightToLeft.Yes) { delta *= -1; } if (delta == 0) { return false; } int[] oldWidths = isColumn ? _table.GetColumnWidths() : _table.GetRowHeights(); int[] newWidths = oldWidths.Clone() as int[]; newWidths[rightIndex] -= delta; newWidths[leftIndex] += delta; if (newWidths[rightIndex] < DesignerUtils.s_minimumSizeDrag || newWidths[leftIndex] < DesignerUtils.s_minimumSizeDrag) { return false; } // now we must renormalize our new widths into the correct sizes _table.SuspendLayout(); int totalPercent = 0; // simplest case: two absolute columns just affect each other. if (_styles[rightIndex].SizeType == SizeType.Absolute && _styles[leftIndex].SizeType == SizeType.Absolute) { // VSWhidbey 465751 // The dimensions reported by GetColumnsWidths() are different // than the style dimensions when the TLP has borders. Instead // of always setting the new size directly based on the reported // sizes, we now base them on the style size if necessary. float newRightSize = newWidths[rightIndex]; float rightStyleSize = (float)_rightStyle.styleProp.GetValue(_rightStyle.style); if (rightStyleSize != oldWidths[rightIndex]) { newRightSize = Math.Max(rightStyleSize - delta, DesignerUtils.s_minimumSizeDrag); } float newLeftSize = newWidths[leftIndex]; float leftStyleSize = (float)_leftStyle.styleProp.GetValue(_leftStyle.style); if (leftStyleSize != oldWidths[leftIndex]) { newLeftSize = Math.Max(leftStyleSize + delta, DesignerUtils.s_minimumSizeDrag); } _rightStyle.styleProp.SetValue(_rightStyle.style, newRightSize); _leftStyle.styleProp.SetValue(_leftStyle.style, newLeftSize); } else if (_styles[rightIndex].SizeType == SizeType.Percent && _styles[leftIndex].SizeType == SizeType.Percent) { for (int i = 0; i < _styles.Count; i++) { if (_styles[i].SizeType == SizeType.Percent) { totalPercent += oldWidths[i]; } } for (int j = 0; j < 2; j++) { int i = j == 0 ? rightIndex : leftIndex; float newSize = (float)newWidths[i] * 100 / totalPercent; PropertyDescriptor prop = TypeDescriptor.GetProperties(_styles[i])[isColumn ? "Width" : "Height"]; prop?.SetValue(_styles[i], newSize); } } else { // mixed - just update absolute int absIndex = _styles[rightIndex].SizeType == SizeType.Absolute ? rightIndex : leftIndex; PropertyDescriptor prop = TypeDescriptor.GetProperties(_styles[absIndex])[isColumn ? "Width" : "Height"]; if (prop is not null) { // VSWhidbey 465751 // The dimensions reported by GetColumnsWidths() are different // than the style dimensions when the TLP has borders. Instead // of always setting the new size directly based on the reported // sizes, we now base them on the style size if necessary. float newAbsSize = newWidths[absIndex]; float curAbsStyleSize = (float)prop.GetValue(_styles[absIndex]); if (curAbsStyleSize != oldWidths[absIndex]) { newAbsSize = Math.Max(absIndex == rightIndex ? curAbsStyleSize - delta : curAbsStyleSize + delta, DesignerUtils.s_minimumSizeDrag); } prop.SetValue(_styles[absIndex], newAbsSize); } else { Debug.Fail("Can't resize - no propertyescriptor for column"); } } _table.ResumeLayout(true); // now determine if the values we pushed into the TLP // actually had any effect. If they didn't, // we delay updating the last mouse position so that // next time a mouse move message comes in the delta is larger. bool updatedSize = true; int[] updatedWidths = isColumn ? _table.GetColumnWidths() : _table.GetRowHeights(); for (int i = 0; i < updatedWidths.Length; i++) { if (updatedWidths[i] == oldWidths[i] && newWidths[i] != oldWidths[i]) { updatedSize = false; } } if (updatedSize) { _lastMouseLoc = mouseLoc; } } else { _lastMouseLoc = mouseLoc; } } return false; } public override bool OnMouseUp(Glyph g, MouseButtons button) { if (_pushedBehavior) { FinishResize(); // commit transaction if (_resizeTransaction is not null) { DesignerTransaction t = _resizeTransaction; _resizeTransaction = null; using (t) { t.Commit(); } _resizeProp = null; } } return false; } internal struct StyleHelper { public int index; public PropertyDescriptor styleProp; public TableLayoutStyle style; } }