Sourcecode - ControlRow
Download ControlRow.cs
// 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
{
/// <summary>
/// The ControlRow automatically aligns all it's children in a row or column.
/// </summary>
public class ControlRow : Panel
{
#region Constructor
/// <summary>
/// Creates a new ControlRow.
/// </summary>
public ControlRow()
{
AutoScroll = true;
}
#endregion
#region Properties
bool arrangeActive = false;
/// <summary>
/// Gets/Sets the if the arranging is active.
/// </summary>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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;
/// <summary>
/// Gets/Sets if the panel should automatically adjust its own size to the size its
/// children need.
/// </summary>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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;
/// <summary>
/// Gets/Sets if the children of this panel should use the full width/height available.
/// </summary>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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;
/// <summary>
/// Gets/Sets the <see cref="ArrangeDirection">Direction</see> used to arrange the
/// child controls.
/// </summary>
/// <exception cref="InvalidEnumArgumentException">
/// Value is not a member of <see cref="Direction"/>.
/// </exception>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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;
/// <summary>
/// Gets/Sets the <see cref="ContentAlignment"/> used to arrange the
/// child controls.
/// </summary>
/// <exception cref="InvalidEnumArgumentException">
/// Value is not a member of <see cref="ContentAlignment"/>.
/// </exception>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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;
/// <summary>
/// Gets/Sets the space between the child controls.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Value is less than zero.
/// </exception>
/// <exception cref="InvokeRequiredException">This method is called from an other thread than the thread that created this control.</exception>
/// <exception cref="ObjectDisposedException">This control was already disposed.</exception>
[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
/// <summary>
/// Gets the combined size of all child items including spacing.
/// </summary>
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();
/// <summary>
/// Raises the <see cref="Control.ControlRemoved">ControlRemoved event</see> and deregisters the
/// control in the <see cref="ControlEventArgs"/> for automatic positioning.
/// </summary>
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);
}
}
/// <summary>
/// Raises the <see cref="Control.ControlRemoved">ControlAdded event</see> and registers the
/// control in the <see cref="ControlEventArgs"/> for automatic positioning.
/// </summary>
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
/// <summary>
/// This method rearranges the internal order of the controls to match their
/// current visual location and then actives the auto-arranging.
/// </summary>
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
}
/// <summary>
/// Specifies a direction for arranging elements.
/// </summary>
public enum Direction
{
/// <summary>
/// Arranges the elements starting from the top, going down.
/// </summary>
TopDown,
/// <summary>
/// Arranges the elements left to right.
/// </summary>
LeftToRight,
/// <summary>
/// Arranges the elements bottom up.
/// </summary>
BottomUp,
/// <summary>
/// Arranges the elements right to left.
/// </summary>
RightToLeft
}
}