using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("OGCLib")]
[assembly: AssemblyDescription("This assembly contains classes for the open group control.")]
[assembly: AssemblyCompany("Igor Ladnik")]
[assembly: AssemblyProduct("Open Group Control")]
[assembly: AssemblyVersion("1.2.*")]
namespace OGCLib
{
///////////////////////////////////////////////////////////////////////////////////
// Enum Orientation
///////////////////////////////////////////////////////////////////////////////////
public enum Orientation
{
Horizontal = 0,
Vertical = 1,
}
///////////////////////////////////////////////////////////////////////////////////
// Class OGControl
///////////////////////////////////////////////////////////////////////////////////
public class OGControl : UserControl
{
// Event/Delegate
// StateChangedEvent
public delegate void StateChangedEventHandler(OGControl source, EventArgs ea);
static public event StateChangedEventHandler StateChangedEvent;
// ErrorEvent
public delegate void ErrorEventHandler(OGControl source, ErrorEventArgs eea);
static public event ErrorEventHandler ErrorEvent;
// Distance (in % of an coorespondent container form's client area size)
// between cursor and appropriate form's side to dock control to this side.
const int dockingDistanceInPerCent = 20;
// Enums
private enum Zone // defines hit test zones for docking
{
None = 0,
Top = 1,
Left = 2,
Right = 4,
Bottom = 8,
TopLeft = Top+Left,
TopRight = Top+Right,
BottomLeft = Bottom+Left,
BottomRight = Bottom+Right,
}
/////////////////////
// Class Caption
/////////////////////
private struct Caption
{
// Variables
internal int height;
internal Rectangle rect;
internal string text;
internal Font font;
internal FontStyle fontStyle;
internal Color colorBack;
internal Color colorBorder;
internal Color colorText;
internal bool clicked;
internal float captionTextSize;
// METHODS
public bool HitTest(MouseEventArgs mea)
{
return rect.Contains(new Point(mea.X, mea.Y));
}
// Draw caption
public void Draw(Graphics grfx, Rectangle clipRect)
{
if (height > 0)
{
Rectangle rect0 = new Rectangle(rect.Left, rect.Top,
rect.Width-1, rect.Height-1);
// Fill
Brush brush = new SolidBrush(colorBack);
grfx.FillRectangle(brush, rect0);
// Text
float indent = 2.0f;
Font fontRegular = new Font(font.FontFamily, captionTextSize, fontStyle);
SizeF textSizeF = grfx.MeasureString(text, fontRegular);
SizeF rectSizeF = new SizeF(rect0.Width-indent, rect0.Height);
if (rectSizeF.Width >= textSizeF.Width)
grfx.DrawString(text, fontRegular, new SolidBrush(colorText), indent, 0);
// Border
grfx.DrawRectangle(new Pen(colorBorder, 1), rect0);
}
}
}
// Variables
protected ArrayList alElement = null; // collection of control's elements (type: IElement)
protected ArrayList alError = null; // collection of error strings (type: string)
protected string xmlConfFileName; // configuration XML file name
private Hashtable htAssembly = null; // collection of loaded elements' assemblies:
// string assemblyName -> Assembly assembly
private ToolTip toolTip = null;
private Cursor standardCursor = null;
private Cursor activeCursor = null;
private IElement hitElement = null; // currently selected element
private MouseEventArgs currentMea = null;
private int controlStatesMax = 0; // max number of the control's states
private int[,] aiControlState = null; // Control State matrix:
// [max number of the control's states, number of elements].
// Contains each element states for each state of control.
private int currentState = 0; // current control's state
private Caption caption;
private bool horizOrient; // current and
private bool prevHorizOrient; // previous orintation of docked control
private bool rotate; // indicates whether control's element should be rotated
// on orientation change
private int angle = 0; // rotation angle
private DockStyle initDock; // initial docking style
// parameters for ghost image enabling while dragging control to redock it
private bool ghost = false;
private Color initBackColor;
// CONSTRUCTOR
public OGControl(string xmlConfFileName)
{
this.xmlConfFileName = xmlConfFileName;
toolTip = new ToolTip();
alElement = new ArrayList();
alError = new ArrayList();
htAssembly = new Hashtable();
// Set background color to Transparent
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
standardCursor = Cursor.Current;
// Create Configurator and initialize control's elements
// with XML config. file
ConfiguratorFactory(true);
InitUpdateGeometry();
caption.clicked = false;
caption.text = Name;
caption.font = Font;
prevHorizOrient = true;
horizOrient = prevHorizOrient;
initBackColor = BackColor;
UpdateAllElements();
}
// PROPERTIES
public IElement HitElement
{
get { return hitElement; }
set { hitElement = value; }
}
public int VisualElementsNumber
{
get { return alElement.Count; }
}
public string ActiveCursorFileName
{
set
{
if (null != activeCursor)
activeCursor.Dispose();
activeCursor = new Cursor(value);
}
}
public int ControlStatesMax
{
get { return controlStatesMax; }
set { controlStatesMax = value; }
}
public int CurrentState
{
get { return currentState; }
set { currentState = value; }
}
public int Angle
{
get { return angle; }
set { angle = value; }
}
public DockStyle InitDock
{
set { initDock = value; }
}
public int CaptionHeight
{
get { return caption.height; }
set { caption.height = value; }
}
internal bool Rotate
{
get { return rotate; }
set { rotate = value; }
}
internal int CaptionTextSize
{
set { caption.captionTextSize = (float)value; }
}
protected MouseEventArgs CurrentMea
{
get { return currentMea; }
}
protected virtual int MaxErrorsNumInBase
{
get { return (int)ErrorType.Count; }
}
private Rectangle CaptionRect
{
get
{
caption.rect = new Rectangle(ClientRectangle.Left, ClientRectangle.Top,
ClientRectangle.Width, CaptionHeight);
return caption.rect;
}
}
// INDEXERS
// Get element by its index from control collection
public virtual IElement this[int index]
{
get
{
if (alElement.Count > 0)
return (IElement)alElement[index];
return null;
}
}
// Get element by its name from control collection
public virtual IElement this[string name]
{
get
{
if (alElement.Count > 0)
foreach (IElement element in alElement)
if (name == element.GetName())
return element;
return null;
}
}
// Get element's state number by control's state number
// and the element's id from Control State matrix
public int this[int ctrlState, int elementId]
{
get
{
try
{
return aiControlState[ctrlState, elementId];
}
catch
{
return -1;
}
}
}
// METHODS
public virtual string GetErrorTag(int i)
{
try
{
return asErrorTag[i];
}
catch
{
return null;
}
}
public virtual string GetErrorMessage(int i)
{
try
{
return (string)alError[i];
}
catch
{
return null;
}
}
public void AddErrorMessage(string errorMessage)
{
alError.Add(errorMessage);
}
// Set a cell[ctrlState, visElemId] of Control State matrix
public void SetMatrixElement(int ctrlState, int visElemId, int val)
{
if (null == aiControlState)
{
if (controlStatesMax > 0 && alElement.Count > 0)
{
aiControlState = new int[controlStatesMax, alElement.Count];
// Initialize all cells with -1
for (int i=0; i<controlStatesMax; i++)
for (int j=0; j<alElement.Count; j++)
aiControlState[i, j] = -1;
}
else
return;
}
aiControlState[ctrlState, visElemId] = val;
}
// Get orintation based in docking style
public Orientation GetOrientation()
{
return DockStyle.Top == Dock || DockStyle.Bottom == Dock
|| DockStyle.Fill == Dock
? Orientation.Horizontal : Orientation.Vertical;
}
// Raise Error event
public void OnError(ErrorEventArgs eea)
{
if (null != ErrorEvent)
ErrorEvent(this, eea);
}
internal void CaptionStyle(Color colorBack, Color colorText, FontStyle fontStyle)
{
caption.colorBack = colorBack;
caption.colorText = colorText;
caption.colorBorder = caption.colorText;
caption.fontStyle = fontStyle;
}
// Create a new control's element (with dynamically loaded element assembly)
// and add it to collection
internal IElement AddElement(string type, string name, Point horizPosition,
Point vertPosition, Size size)
{
IElement element = null;
string assemblyName = type + "Lib"; // Name convention for loaded element assemblies.
// The assemblies assumed in working directory.
Assembly assembly = (Assembly)htAssembly[assemblyName];
if (null == assembly)
{
// This assembly has not been loaded yet
try
{
assembly = Assembly.Load(assemblyName);
if (null == assembly)
throw(new Exception());
// Add newly loaded assembly to collection
htAssembly.Add(assemblyName, assembly);
}
catch
{
// Load assembly error
ErrorType errorType = ErrorType.LoadAssembly;
string errorMessage = string.Format(GetErrorMessage((int)errorType), assemblyName);
OnError(new ErrorEventArgs(ErrorSeverity.Error, errorMessage));
}
}
if (null != assembly)
{
string className = assemblyName + ".Factory"; // name convention for Factory class
// in the assembly
// Create instance of assembly's Factory class
object factory = assembly.CreateInstance(className);
if (null != factory)
// Create control's element with assembly's Factory
element = ((IFactory)factory).CreateElement(this);
}
if (null != element)
{
// Initialize newly creted element and add it to collection
element.Initialize(name, horizPosition, vertPosition, size);
alElement.Add(element);
}
return element;
}
// In the above method, when an element assembly is loaded, to create an IElement object
// it is possible to loop through the assembly's types (with Reflection technique) looking for a type
// that implements IElement. But I opted for IFactory mechanismdue to the followng.
// Although it's unlikely, several types in the assembly may implement IElement interface.
// In this case the assembly may decide by itself which type instantiate. Factory mechanism resolves
// this (almost pure theoretical case).
// Factory to create Configurator object (to support Configurator-derived classes, if any
protected virtual void ConfiguratorFactory(bool operationLoad)
{
Configurator configurator =
new Configurator(this, xmlConfFileName, operationLoad);
}
protected IElement HitTest(MouseEventArgs mea)
{
IElement theLastInZOrderVisualElement = null;
if (alElement.Count > 0)
foreach (IElement element in alElement)
if (element.HitTest(mea))
theLastInZOrderVisualElement = element;
return theLastInZOrderVisualElement;
}
// ToolTip support
protected void ToolTipHandling(IElement element)
{
if (null == currentMea)
return;
if (MouseButtons.None == currentMea.Button)
{
// None mouse button is pressed
string tip = ".";
if (null != element)
{
// Set up the ToolTip text for the control
tip = ConstructTip(element.GetName(), element.GetStateName());
if (!toolTip.Active)
{
toolTip.Dispose();
toolTip = new ToolTip();
toolTip.AutoPopDelay = 5000;
toolTip.InitialDelay = 1000;
toolTip.ReshowDelay = 500;
}
toolTip.Active = true;
}
else
toolTip.Active = false;
toolTip.SetToolTip(this, tip);
}
}
private Orientation GetInitOrientation()
{
return DockStyle.Top == initDock || DockStyle.Bottom == initDock
|| DockStyle.Fill == initDock
? Orientation.Horizontal : Orientation.Vertical;
}
// Convert a point to Parent Form coordinates
private Point PointToParentClient(int x, int y)
{
Point ptAtControl = new Point(x, y);
Point ptAtScreen = PointToScreen(ptAtControl);
return Parent.PointToClient(ptAtScreen);
}
private void InitUpdateGeometry()
{
if (alElement.Count > 0)
{
Orientation orientation = GetOrientation();
if (GetInitOrientation() != orientation)
{
horizOrient = (Orientation.Horizontal == GetOrientation());
TransposeRotate();
}
}
}
private void UpdateGeometry()
{
if (prevHorizOrient != horizOrient && alElement.Count > 0)
TransposeRotate();
}
private void TransposeRotate()
{
if (rotate)
angle = horizOrient ? 90 : -90;
else
angle = 0;
foreach (IElement element in alElement)
element.UpdateGeometry();
}
// Update current state of all elements
private void UpdateAllElements()
{
if (alElement.Count > 0 && null != aiControlState)
foreach (IElement element in alElement)
element.SetCurrentState(this[currentState, element.GetId()]);
Invalidate();
}
// Overriden
protected override void OnMouseDown(MouseEventArgs mea)
{
currentMea = mea;
if (MouseButtons.Left == mea.Button)
{
if (CaptionHeight > 0)
caption.clicked = caption.HitTest(mea);
if (!caption.clicked)
hitElement = HitTest(mea);
}
base.OnMouseDown(mea);
}
protected override void OnMouseUp(MouseEventArgs mea)
{
GhostOff();
currentMea = mea;
// Check whether caption is clicked
if (caption.clicked)
{
caption.clicked = caption.HitTest(mea);
if (!caption.clicked)
ChangeDockStyle(mea);
}
else
{
// Check whether visual object is clicked
if (null != hitElement)
// Mouse was down at hitElement
if (MouseButtons.Left == mea.Button)
{
IElement element = HitTest(mea);
if (null != element)
{
if (element == hitElement)
{
// Down and Up at the SAME Visual Elememt
int newControlState = -1;
if (GetAndAssertNewControlState(hitElement, ref newControlState))
ChangeState(newControlState);
}
}
}
}
Cursor.Current = standardCursor;
caption.clicked = false;
base.OnMouseUp(mea);
}
protected override void OnMouseMove(MouseEventArgs mea)
{
currentMea = mea;
IElement element = null;
// Check whether caption is clicked
if (caption.clicked)
{
GhostOn();
ChangeDockStyle(mea);
}
else
{
if (!caption.HitTest(mea))
{
element = HitTest(mea);
if (null != element)
{
int wouldBeNewControlState = -1;
if (GetAndAssertNewControlState(element, ref wouldBeNewControlState))
Cursor.Current = activeCursor;
}
}
}
ToolTipHandling(element);
base.OnMouseMove(mea);
}
protected override void OnResize(EventArgs ea)
{
Invalidate(CaptionRect);
base.OnResize(ea);
}
protected override void OnPaint(PaintEventArgs pea)
{
Graphics grfx = pea.Graphics;
// Draw all visual elements. This block is called even in case
// of ghost placeholde rectangle to make invisible elements
// derived from standard controls, like e.g. combo box.
if (alElement.Count > 0)
foreach (IElement element in alElement)
element.Draw(grfx, !ghost);
if (ghost)
DrawGhost(grfx); // draw ghost
else
caption.Draw(grfx, pea.ClipRectangle); // draw caption
}
private void DrawGhost(Graphics grfx)
{
grfx.Clear(Parent.BackColor);
Color ghostColor = (Color.DarkGray == Parent.BackColor)
? Color.LightGray : Color.DarkGray;
Pen pen = new Pen(ghostColor, 9);
pen.LineJoin = LineJoin.Round;
//pen.DashStyle = DashStyle.Dash; // uncomment for Dash style for a ghost rectangle
grfx.DrawRectangle(pen, ClientRectangle);
}
private bool GetAndAssertNewControlState(IElement element, ref int newControlState)
{
bool br = false;
if (null != element)
{
newControlState = element.GetNewControlState();
if (newControlState != currentState &&
newControlState != -1)
br = true;
}
return br;
}
// Switch on ghost placeholder mode while dragging control with its caption
private void GhostOn()
{
if (!ghost)
{
initBackColor = BackColor;
BackColor = Parent.BackColor;
ghost = true;
}
Invalidate();
}
// Switch off ghost placeholder mode
private void GhostOff()
{
if (ghost)
{
BackColor = initBackColor;
ghost = false;
Invalidate();
}
}
// Actions to change dock style
private void ChangeDockStyle(MouseEventArgs mea)
{
if (DockStyle.Fill != Dock)
{
prevHorizOrient = (Orientation.Horizontal == GetOrientation());
// Calculate new docking style according to given rules.
// Hit point is converted to Parent client coordinates.
CalculateDock(PointToParentClient(mea.X, mea.Y));
horizOrient = (Orientation.Horizontal == GetOrientation());
UpdateGeometry();
// Save changes to XML config. file
ConfiguratorFactory(false);
}
else
horizOrient = prevHorizOrient;
}
// Virtual
// Actiona related to change control's state
public virtual bool ChangeState(int newCurrentState)
{
bool br = false;
if (-1 != newCurrentState)
{
int prevState = currentState;
currentState = newCurrentState;
if (prevState != currentState)
{
UpdateAllElements();
// Virtual method (currently empty) to let derived control class add
// its specific actions, if required
OnChangeState();
if (null != StateChangedEvent)
// Raise StateChanged event
StateChangedEvent(this, new EventArgs());
// Save changes to XML config. file
ConfiguratorFactory(false);
br = true;
}
}
return br;
}
// Calculate new docking style according to given rules
protected virtual void CalculateDock(
Point position /*hit point converted to Parent client coordinates*/)
{
if (Parent.ClientRectangle.Contains(position))
{
Rectangle leftRect = new Rectangle(
Parent.ClientRectangle.Left, Parent.ClientRectangle.Top,
Parent.ClientRectangle.Left +
Parent.ClientSize.Width * dockingDistanceInPerCent / 100,
Parent.ClientRectangle.Bottom);
Rectangle rightRect = new Rectangle(
Parent.ClientRectangle.Right -
Parent.ClientSize.Width * dockingDistanceInPerCent / 100,
Parent.ClientRectangle.Top,
Parent.ClientRectangle.Right, Parent.ClientRectangle.Bottom);
Rectangle topRect = new Rectangle(
Parent.ClientRectangle.Left, Parent.ClientRectangle.Top,
Parent.ClientRectangle.Right,
Parent.ClientRectangle.Top +
Parent.ClientSize.Height * dockingDistanceInPerCent / 100);
Rectangle bottomRect = new Rectangle(
Parent.ClientRectangle.Left,
Parent.ClientRectangle.Bottom -
Parent.ClientSize.Height * dockingDistanceInPerCent / 100,
Parent.ClientRectangle.Right, Parent.ClientRectangle.Bottom);
int dock = (int)Zone.None;
if (topRect.Contains(position))
dock += (int)Zone.Top;
if (leftRect.Contains(position))
dock += (int)Zone.Left;
if (rightRect.Contains(position))
dock += (int)Zone.Right;
if (bottomRect.Contains(position))
dock += (int)Zone.Bottom;
switch ((Zone)dock)
{
case Zone.Top: Dock = DockStyle.Top; break;
case Zone.Left: Dock = DockStyle.Left; break;
case Zone.Right: Dock = DockStyle.Right; break;
case Zone.Bottom: Dock = DockStyle.Bottom; break;
case Zone.TopLeft:
Dock = (position.X - Parent.ClientRectangle.Left <
position.Y - Parent.ClientRectangle.Top)
? DockStyle.Left : DockStyle.Top;
break;
case Zone.TopRight:
Dock = (Parent.ClientRectangle.Right - position.X <
position.Y - Parent.ClientRectangle.Top)
? DockStyle.Right : DockStyle.Top;
break;
case Zone.BottomLeft:
Dock = (position.X - Parent.ClientRectangle.Left <
Parent.ClientRectangle.Bottom - position.Y)
? DockStyle.Left : DockStyle.Bottom;
break;
case Zone.BottomRight:
Dock = (Parent.ClientRectangle.Right - position.X <
Parent.ClientRectangle.Bottom - position.Y)
? DockStyle.Right : DockStyle.Bottom;
break;
}
}
}
// Allow derived control class to add its specific actions, if required
protected virtual void OnChangeState()
{
}
// Allow derived control class to construct element tips based on
// the element name and state
protected virtual string ConstructTip(string visualElementName, string stateName)
{
return visualElementName;
}
// Read Only Error Tag Variable. Correspond to those in OGCErrorMessages.xml file.
private readonly string[] asErrorTag =
{
"ErrorMessages",
"Picture",
"LoadConfigFile",
"ProcessNode",
"ReadingTag",
"WritingTag",
"TagState",
"LoadAssembly",
"StateTransition",
"ControlStatesMatrix",
};
}
///////////////////////////////////////////////////////////////////////////////////
// Enum ErrorType. Corresponds to asErrorTag array.
///////////////////////////////////////////////////////////////////////////////////
public enum ErrorType
{
None = -1,
ErrorMessages = 0,
Picture = 1,
LoadConfigFile = 2,
ProcessNode = 3,
ReadingTag = 4,
WritingTag = 5,
TagState = 6,
LoadAssembly = 7,
StateTransition = 8,
ControlStatesMatrix = 9,
Count = 10,
}
///////////////////////////////////////////////////////////////////////////////////
// Enum ErrorSeverity
///////////////////////////////////////////////////////////////////////////////////
public enum ErrorSeverity
{
None = 0,
Warning = 1,
Error = 2,
}
///////////////////////////////////////////////////////////////////////////////////
// class ErrorEventArgs
///////////////////////////////////////////////////////////////////////////////////
public class ErrorEventArgs
{
// Variables
public ErrorSeverity errorSeverity = ErrorSeverity.None;
public string errorMessage = null;
// Constructor
public ErrorEventArgs(ErrorSeverity errorSeverity, string errorMessage)
{
this.errorSeverity = errorSeverity;
this.errorMessage = errorMessage;
}
}
///////////////////////////////////////////////////////////////////////////////////
// Interface IElement
// Each element of control has to implement this interface.
///////////////////////////////////////////////////////////////////////////////////
public interface IElement
{
// Initialize the element
void Initialize(string name, Point horizPosition, Point vertPosition, Size size);
// Set State Transition matrix cell
void SetControlStateTransitionElement(int currCtrlState, // current control's state (row)
int currState, // current element's state (column)
int nextCtrlState); // next control's state (value)
// Add state to the element
int AddState(string name,
string pictureName, // name of state's picture file
string ext); // extension state's of picture file
// Get new control's state on activation of the element
int GetNewControlState();
// Set current element's state according to a control's state
void SetCurrentState(int currentControlState);
// true - for pictorial elements (like OGCVisualElement),
// false - for elements derived from standard (like OGCComboBox)
bool HasImage();
// Get element name
string GetName();
// Get element id (currently, serial number in the control's elements collection)
int GetId();
// Get name of the current state of the element
string GetStateName();
// Get number of possible changes in control,
// e.g., number of items in list - for combo box
// 1 - for press button and visual element
int GetNextStatesNum();
// Update geometry of the element according to current orientation
void UpdateGeometry();
// Indicate whether cursor position is currently over the element
bool HitTest(MouseEventArgs mea);
// Draw the element
void Draw(Graphics grfx,
bool show); // show/hide the element
}
///////////////////////////////////////////////////////////////////////////////////
// Interface IFactory
// implemented by each loadable element assembly in order to instantiate
// correspondent element.
///////////////////////////////////////////////////////////////////////////////////
public interface IFactory
{
IElement CreateElement(OGControl ogControl);
}
}