//
// Designed and Developed by Norm Almond
// 15 September 2004
// Version 1.0.0
// www.software-kinetics.co.uk
//
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
namespace TextAlignCtrl
{
/// <summary>
/// Text Alignment Control
/// </summary>
[ToolboxBitmap(typeof(TextAlign), "images.TextAlign.bmp"),
System.ComponentModel.DefaultPropertyAttribute("Angle")]
public class TextAlign : System.Windows.Forms.UserControl
{
#region Events
public delegate void OnAngleChangedHandler(object sender, int Angle);
[Category("Action")]
[Description("Fired when angle has changed")]
public event OnAngleChangedHandler OnAngleChangedEvent;
#endregion
#region Variable Declarations
private bool _trackMouse = false;
private int _Angle = 0;
private const int PegSize = 3;
private const int _Offset = 10;
#endregion
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public TextAlign(System.ComponentModel.IContainer container)
{
_Init();
container.Add(this);
InitializeComponent();
}
public TextAlign()
{
_Init();
InitializeComponent();
}
/// <summary>
/// Common Contructor Code
/// </summary>
private void _Init()
{
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
UpdateStyles();
}
protected override bool ProcessDialogKey(Keys keyData)
{
if (!Enabled)
return base.ProcessDialogKey (keyData);
int delta = Angle;
switch (keyData)
{
case Keys.Home:
delta = 0;
break;
case (Keys.Down | Keys.Control):
delta -= 15;
delta = ((delta / 15) * 15);
break;
case Keys.Down:
delta -= 1;
break;
case (Keys.Up | Keys.Control):
delta += 15;
delta = ((delta / 15) * 15);
break;
case Keys.Up:
delta += 1;
break;
default:
return base.ProcessDialogKey (keyData);
}
Angle = delta;
return false;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
//
// TextAlign
//
this.BackColor = System.Drawing.SystemColors.Window;
this.Name = "TextAlign";
this.Size = new System.Drawing.Size(120, 176);
this.SizeChanged += new System.EventHandler(this.TextAlign_SizeChanged);
this.Enter += new System.EventHandler(this.TextAlign_Enter);
this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.TextAlign_MouseUp);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.TextAlign_Paint);
this.Leave += new System.EventHandler(this.TextAlign_Leave);
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.TextAlign_MouseMove);
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.TextAlign_MouseDown);
}
#endregion
#region Private Functions
private void _Render(Graphics g, Rectangle rcClient)
{
// Render Background
g.FillRectangle(Enabled ? new SolidBrush(this.BackColor) : SystemBrushes.Control, rcClient);
// Render Frame
g.DrawRectangle(Focused ? SystemPens.Highlight : SystemPens.ControlDark, rcClient.X, rcClient.Y, rcClient.Width-1, rcClient.Height-1);
Point ptCenter = new Point(_Offset, rcClient.Height / 2);
StringFormat format = new StringFormat(StringFormat.GenericDefault);
format.HotkeyPrefix = HotkeyPrefix.None;
SizeF sz = g.MeasureString("Text", Control.DefaultFont, Point.Empty,format);
Rectangle rcText = rcClient;
rcText.X += 2; // Indent from Left
// Rotate text on given Angle
g.TranslateTransform (ptCenter.X, ptCenter.Y);
g.RotateTransform(-_Angle);
g.DrawString("Text", Control.DefaultFont, SystemBrushes.WindowText, new Point(0,(int)-(sz.Height/2)), format);
g.ResetTransform();
// Render Dial
Rectangle rc;
// Render tick @ 15 degree intervals
for (int tick=-90; tick <= 90; tick+=15)
{
Point ptSelectStart = _AngleToPoint(ptCenter, (double)_Angle, ptCenter.X + sz.Width - 8);
Point ptSelectEnd = _AngleToPoint(ptCenter , (double)_Angle, rcClient.Width - 24);
g.DrawLine(Pens.Black,ptSelectStart,ptSelectEnd);
Point pt = _AngleToPoint(ptCenter, tick, rcClient.Width - 18);
rc = new Rectangle(pt, Size.Empty);
rc.Inflate(1,1);
g.FillRectangle(Brushes.Black, rc);
if ((tick % 45) == 0)
{
using (GraphicsPath myPath = new GraphicsPath())
{
myPath.AddLine(pt.X, pt.Y - PegSize, pt.X + PegSize, pt.Y);
myPath.AddLine(pt.X + PegSize, pt.Y, pt.X, pt.Y + PegSize);
myPath.AddLine(pt.X, pt.Y + PegSize, pt.X - PegSize, pt.Y);
myPath.CloseFigure();
g.FillPath(tick == _Angle ? Brushes.Red : Brushes.Black, myPath);
myPath.AddLine(pt.X, pt.Y - PegSize, pt.X + PegSize, pt.Y);
myPath.AddLine(pt.X + PegSize, pt.Y, pt.X, pt.Y + PegSize);
myPath.AddLine(pt.X, pt.Y + PegSize, pt.X - PegSize, pt.Y);
myPath.CloseFigure();
g.DrawPath(Pens.Black, myPath);
}
}
}
}
/// <summary>
/// Convert Angle to point
/// </summary>
/// <param name="ptOffset"></param>
/// <param name="angle"></param>
/// <param name="radius"></param>
/// <returns></returns>
private Point _AngleToPoint(Point ptOffset, double angle, double radius)
{
double radians = angle / (180.0 / Math.PI);
int x = ptOffset.X + (int)((double)radius* Math.Cos(radians));
int y = ptOffset.Y - (int)((double)radius * Math.Sin(radians));
return new Point(x,y);
}
private double _ArcTangent(double ratio)
{
double angle = Math.Atan(ratio);
// convert radians to degrees
return angle * (180.0 / Math.PI);;
}
private double _ArcTangent(double x, double y)
{
double angle = 0.0;
// both x and y are 0
if ((x == 0.0) && (y == 0.0))
{
// Set tthe angle to Zero degrees
angle = 0.0;
}
else if (x == 0.0)
{
// If we're on the y axis line
angle = (y < 0.0) ? -90 : 90.0;
}
else
{
// else neither x or y is zero
// Find the arc-tangent of y / x in degrees
angle = _ArcTangent((y / x));
// if x is negative
if (x < 0.0)
{
angle += (y > 0.0) ? 180.0 : -180.0;
}
if (angle < -90)
angle = -90;
if (angle > 90)
angle = 90;
}
return angle;
}
/// <summary>
/// Converts a point to angle
/// </summary>
/// <param name="point"></param>
/// <param name="center"></param>
/// <returns></returns>
private double PointToAngle(Point point, Point center)
{
// Calculate the position user click relative to the center
int x = point.X - center.X;
int y = center.Y - point.Y;
//Convert xy position to an angle.
double angle = _ArcTangent( (double) x, (double) y );
return angle;
}
/// <summary>
/// Render Control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextAlign_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
_Render(e.Graphics, this.ClientRectangle);
}
/// <summary>
/// Update Angle in relation to mouse
/// </summary>
/// <param name="point"></param>
private void _UpdateAngle(Point point)
{
Rectangle rcClient = this.ClientRectangle;
Point ptCenter = new Point(_Offset, rcClient.Height / 2);
if (_trackMouse)
{
//Convert xy position to an angle.
Angle = (int)(PointToAngle(point, ptCenter));
}
}
/// <summary>
/// Track mouse as user moves mouse over control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextAlign_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (!Enabled)
return;
_UpdateAngle(new Point(e.X, e.Y));
Invalidate();
}
/// <summary>
/// End tracking mouse
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextAlign_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (!Enabled)
return;
_UpdateAngle(new Point(e.X, e.Y));
_trackMouse = false;
Capture = false;
Invalidate();
}
/// <summary>
/// Begin Tracking mouse
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextAlign_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (!Enabled)
return;
_trackMouse = true;
this.Capture = true;
Focus();
_UpdateAngle(new Point(e.X, e.Y));
Invalidate();
}
/// <summary>
/// Control size changed, refresh control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextAlign_SizeChanged(object sender, System.EventArgs e)
{
Refresh();
}
#endregion
private void TextAlign_Enter(object sender, System.EventArgs e)
{
Invalidate();
}
private void TextAlign_Leave(object sender, System.EventArgs e)
{
Invalidate();
}
#region Public Methods
/// <summary>
/// Get/Set Angle for control
/// </summary>
[Category("TextAlign")]
[Description("Gets/Sets angle of text")]
public int Angle
{
get
{
return _Angle;
}
set
{
if (!Enabled)
return;
if (value < -90 || value > 90)
return;
_Angle = value;
Invalidate();
if (OnAngleChangedEvent != null)
OnAngleChangedEvent(this, _Angle);
}
}
protected virtual void OnAngleChanged(object sender, int angle)
{
if (OnAngleChangedEvent != null)
OnAngleChangedEvent(this, _Angle);
}
#endregion
}
}