| File: System\Windows\Forms\Layout\FlowLayout.ContainerProxy.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; namespace System.Windows.Forms.Layout; internal partial class FlowLayout { /// <summary> /// The goal of the FlowLayout Engine is to always layout from left to right. In order to achieve different /// flow directions we have "Proxies" for the Container (the thing laying out) and for setting the bounds of the /// child elements. /// /// We have a base ContainerProxy, and derived proxies for all of the flow directions. In order to achieve flow /// direction of RightToLeft, the RightToLeft container proxy detects when we're going to set the bounds /// and translates it to the right. /// /// In order to do a vertical flow, such as TopDown, we pretend we're laying out horizontally. The main way this is /// achieved is through the use of the VerticalElementProxy, which flips all rectangles and sizes. /// /// In order to do BottomUp, we combine the same techniques of TopDown with the RightToLeft flow. That is, /// we override the bounds, and translate from left to right, AND use the VerticalElementProxy. /// /// A final note: This layout engine does all its RightToLeft translation itself. It does not support /// WS_EX_LAYOUTRTL (OS mirroring). /// </summary> private class ContainerProxy { private readonly IArrangedElement _container; private readonly bool _isContainerRTL; private Rectangle _displayRect; private ElementProxy? _elementProxy; public ContainerProxy(IArrangedElement container) { _container = container; _isContainerRTL = false; if (_container is Control control) { _isContainerRTL = control.RightToLeft == RightToLeft.Yes; } } public virtual Rectangle Bounds { set { if (IsContainerRTL) { // Offset the X coordinate. if (IsVertical) { // Offset the Y value here, since it is really the X value. value.Y = DisplayRect.Bottom - value.Bottom; } else { value.X = DisplayRect.Right - value.Right; } if (Container is FlowLayoutPanel flp) { Point ptScroll = flp.AutoScrollPosition; if (ptScroll != Point.Empty) { Point pt = new(value.X, value.Y); if (IsVertical) { // Offset the Y value here, since it is really the X value. pt.Offset(0, ptScroll.X); } else { pt.Offset(ptScroll.X, 0); } value.Location = pt; } } } ElementProxy.Bounds = value; } } public IArrangedElement Container => _container; /// <summary> /// Returns the display rectangle of the container - this will be flipped if the layout /// is a vertical layout. /// </summary> public Rectangle DisplayRect { get => _displayRect; set { if (_displayRect != value) { // flip the displayRect since when we do layout direction TopDown/BottomUp, we layout the controls // on the flipped rectangle as if our layout direction were LeftToRight/RightToLeft. In this case // we can save some code bloat _displayRect = LayoutUtils.FlipRectangleIf(IsVertical, value); } } } /// <summary> /// Returns the element proxy to use. A vertical element proxy will typically flip /// all the sizes and rectangles so that it can fake being laid out in a /// horizontal manner. /// </summary> public ElementProxy ElementProxy { get { _elementProxy ??= IsVertical ? new VerticalElementProxy() : new ElementProxy(); return _elementProxy; } } protected bool IsContainerRTL => _isContainerRTL; /// <summary> /// Specifies if we're TopDown or BottomUp and should use the VerticalElementProxy to /// translate /// </summary> protected virtual bool IsVertical => false; /// <summary> /// Used when you want to translate from right to left, but preserve Margin.Right /// and Margin.Left. /// </summary> protected Rectangle RTLTranslateNoMarginSwap(Rectangle bounds) { Rectangle newBounds = bounds; newBounds.X = DisplayRect.Right - bounds.X - bounds.Width + ElementProxy.Margin.Left - ElementProxy.Margin.Right; // Since DisplayRect.Right and bounds.X are both adjusted for the AutoScrollPosition, we need add it back here. if (Container is FlowLayoutPanel flp) { Point ptScroll = flp.AutoScrollPosition; if (ptScroll != Point.Empty) { Point pt = new(newBounds.X, newBounds.Y); if (IsVertical) { // We need to treat Vertical a little differently. It really helps if you draw this out. // Remember that when we layout BottomUp, we first layout TopDown, then call this method. // When we layout TopDown we layout in flipped rectangles. I.e. x becomes y, y becomes x, // height becomes width, width becomes height. We do our layout, then when we eventually // set the bounds of the child elements, we flip back. Thus, x will eventually // become y. We need to adjust for scrolling - but only in the y direction - // and since x becomes y, we adjust x. But since AutoScrollPosition has not been swapped, // we need to use its Y coordinate when offsetting. pt.Offset(ptScroll.Y, 0); } else { pt.Offset(ptScroll.X, 0); } newBounds.Location = pt; } } return newBounds; } } }