// Copyright (C) 2004 Daniel Grunwald // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // For more information read the file "LICENSE.txt". using System; using System.Collections; using System.Drawing; using System.ComponentModel; using System.Windows.Forms; using System.Net; namespace Grunwald.Gui.Controls { /// /// The ControlRow automatically aligns all it's children in a row or column. /// public class ControlRow : Panel { #region Constructor /// /// Creates a new ControlRow. /// public ControlRow() { AutoScroll = true; } #endregion #region Properties bool arrangeActive = false; /// /// Gets/Sets the if the arranging is active. /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(false), Description("Specifies if the automatic arranging is active. You should use ActivateArrange() when you use the form designer!")] public bool ArrangeActive { get { return arrangeActive; } set { if (arrangeActive == value) return; arrangeActive = value; if (arrangeActive) ReArrange(); } } bool autoSize = false; /// /// Gets/Sets if the panel should automatically adjust its own size to the size its /// children need. /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(false), Description("Specifies if the panel should automatically adjust its own size to the size its children need.")] public bool AutoSize { get { return autoSize; } set { if (autoSize == value) return; autoSize = value; if (autoSize) ReArrange(); } } bool fullWidth; /// /// Gets/Sets if the children of this panel should use the full width/height available. /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(false), Description("Specifies if the children of this panel should use the full width/height available.")] public bool FullWidth { get { return fullWidth; } set { if (fullWidth == value) return; fullWidth = value; ReArrange(); } } Direction direction = Direction.TopDown; /// /// Gets/Sets the Direction used to arrange the /// child controls. /// /// /// Value is not a member of . /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(Direction.TopDown), Description("Specifies the direction used to arrange the child controls.")] public Direction Direction { get { return direction; } set { if (direction == value) return; if (!Enum.IsDefined(typeof(Direction), value)) throw new InvalidEnumArgumentException(); direction = value; ReArrange(); } } ContentAlignment contentAlignment = ContentAlignment.TopCenter; /// /// Gets/Sets the used to arrange the /// child controls. /// /// /// Value is not a member of . /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(ContentAlignment.TopCenter), Description("Specifies the content alignment used to arrange the child controls.")] public ContentAlignment ContentAlignment { get { return contentAlignment; } set { if (contentAlignment == value) return; if (!Enum.IsDefined(typeof(ContentAlignment), value)) throw new InvalidEnumArgumentException(); contentAlignment = value; ReArrange(); } } int space = 4; /// /// Gets/Sets the space between the child controls. /// /// /// Value is less than zero. /// /// This method is called from an other thread than the thread that created this control. /// This control was already disposed. [Bindable(true), Category("Layout"), DefaultValue(4), Description("Specifies the space between the child controls.")] public int Space { get { return space; } set { if (space == value) return; if (space < 0) throw new ArgumentOutOfRangeException("space", value, "Space can't be less than zero."); space = value; ReArrange(); } } #endregion #region GetChildSize /// /// Gets the combined size of all child items including spacing. /// public Size GetChildSize() { bool vertical = direction == Direction.TopDown || direction == Direction.BottomUp; int minWidth = 0; // minimal "width" (=size orthogonal to the arrange direction) int height = 0; // "height" (=size in arrange direction) foreach (Control ctl in controls) { if (!ctl.Visible) continue; minWidth = Math.Max(minWidth, vertical ? ctl.Width : ctl.Height); if (height > 0) height += space; height += vertical ? ctl.Height : ctl.Width; } if (vertical) return new Size(minWidth, height); else return new Size(height, minWidth); } #endregion #region ReArrange int arrangeCount; private void ReArrange() { Check(); if (!arrangeActive) { return; } if (!this.Visible) return; int controlcount = controls.Count; ++arrangeCount; // Console.Write(arrangeCount + " "); if (controlcount == 0) return; Size size = GetChildSize(); Size sizeWithMargin = new Size(size.Width + 2 * space, size.Height + 2 * space); Size available; if (autoSize) ClientSize = sizeWithMargin; available = ClientSize; // gets alignment values: 0 = left/top, 1 = center, 2 = right/bottom int xLayout = GetXLayout(); int yLayout = GetYLayout(); Size useSize = new Size(Math.Max(available.Width, sizeWithMargin.Width), Math.Max(available.Height, sizeWithMargin.Height)); bool reverse = direction == Direction.BottomUp || direction == Direction.RightToLeft; bool vertical = direction == Direction.TopDown || direction == Direction.BottomUp; int pos; // start position if (vertical) pos = DoLayout(yLayout, useSize.Height, size.Height); else pos = DoLayout(xLayout, useSize.Width, size.Width); for (int i = reverse ? controlcount - 1 : 0; i >= 0 && i < controlcount; i += reverse ? -1 : 1) { Control ctl = (Control)controls[i]; if (!ctl.Visible) continue; AnchorStyles anchor = AnchorStyles.None; if (xLayout == 0) anchor |= AnchorStyles.Left; else if (xLayout == 2) anchor |= AnchorStyles.Right; if (yLayout == 0) anchor |= AnchorStyles.Top; else if (yLayout == 2) anchor |= AnchorStyles.Bottom; if (ctl.Anchor != anchor) ctl.Anchor = anchor; int top = ctl.Top; int left = ctl.Left; int width = ctl.Width; int height = ctl.Height; if (vertical) { left = DoLayout(xLayout, useSize.Width, ctl.Width); top = pos; } else { left = pos; top = DoLayout(yLayout, useSize.Height, ctl.Height); } ctl.Bounds = new Rectangle(left, top, width, height); pos += space + (vertical ? ctl.Height : ctl.Width); } } private int DoLayout(int layout, int available, int use) { if (layout == 0) return space; else if (layout == 1) return (available - use) / 2; else return available - use - space; } private int GetXLayout() { switch (contentAlignment) { case ContentAlignment.TopLeft: case ContentAlignment.MiddleLeft: case ContentAlignment.BottomLeft: return 0; case ContentAlignment.TopCenter: case ContentAlignment.MiddleCenter: case ContentAlignment.BottomCenter: return 1; case ContentAlignment.TopRight: case ContentAlignment.MiddleRight: case ContentAlignment.BottomRight: return 2; default: throw new InvalidEnumArgumentException(); } } private int GetYLayout() { switch (contentAlignment) { case ContentAlignment.TopCenter: case ContentAlignment.TopLeft: case ContentAlignment.TopRight: return 0; case ContentAlignment.MiddleCenter: case ContentAlignment.MiddleLeft: case ContentAlignment.MiddleRight: return 1; case ContentAlignment.BottomCenter: case ContentAlignment.BottomLeft: case ContentAlignment.BottomRight: return 2; default: throw new InvalidEnumArgumentException(); } } private void Check() { if (base.IsDisposed) throw new ObjectDisposedException("ErrorReporter"); if (base.InvokeRequired) throw new InvokeRequiredException(); } #endregion #region Control Events private void ControlSizeChanged(object sender, EventArgs e) { Control ctl = (Control)sender; if (!ctl.Visible) return; //ReArrange(); } private void ControlVisibleChanged(object sender, EventArgs e) { ReArrange(); } private void ControlSettingsChanged(object sender, EventArgs e) { ReArrange(); } #endregion #region Overridden Events ArrayList controls = new ArrayList(); /// /// Raises the ControlRemoved event and deregisters the /// control in the for automatic positioning. /// protected override void OnControlRemoved(System.Windows.Forms.ControlEventArgs e) { try { Control ctl = e.Control; controls.Remove(ctl); ctl.SizeChanged -= new EventHandler(ControlSizeChanged); ctl.VisibleChanged -= new EventHandler(ControlVisibleChanged); } finally { base.OnControlRemoved(e); } } /// /// Raises the ControlAdded event and registers the /// control in the for automatic positioning. /// protected override void OnControlAdded(System.Windows.Forms.ControlEventArgs e) { try { Control ctl = e.Control; controls.Add(ctl); ctl.SizeChanged += new EventHandler(ControlSizeChanged); ctl.VisibleChanged += new EventHandler(ControlVisibleChanged); } finally { base.OnControlAdded(e); } } #endregion #region ActivateArrange /// /// This method rearranges the internal order of the controls to match their /// current visual location and then actives the auto-arranging. /// public void ActivateArrange() { Check(); bool reverse = direction == Direction.BottomUp || direction == Direction.RightToLeft; bool vertical = direction == Direction.TopDown || direction == Direction.BottomUp; controls.Sort(new ControlComparer(reverse, vertical)); ArrangeActive = true; } private class ControlComparer : IComparer { bool reverse; bool vertical; public ControlComparer(bool reverse, bool vertical) { this.reverse = reverse; this.vertical = vertical; } int IComparer.Compare(object x, object y) { Control a = x as Control; Control b = y as Control; /*if (a == b) return 0; if (a == null) return 1; if (b == null) return -1;*/ if (vertical) { if (a.Top == b.Top) return 0; return ((a.Top > b.Top) ^ reverse) ? 1 : -1; } else { if (a.Left == b.Left) return 0; return ((a.Left > b.Left) ^ reverse) ? 1 : -1; } } } #endregion } /// /// Specifies a direction for arranging elements. /// public enum Direction { /// /// Arranges the elements starting from the top, going down. /// TopDown, /// /// Arranges the elements left to right. /// LeftToRight, /// /// Arranges the elements bottom up. /// BottomUp, /// /// Arranges the elements right to left. /// RightToLeft } }