1102 lines
41 KiB
C#
1102 lines
41 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.Drawing.Text;
|
|
|
|
namespace DevComponents.DotNetBar.Controls
|
|
{
|
|
/// <summary>
|
|
/// Analog clock control.
|
|
/// </summary>
|
|
[Description("Analog Clock Control"), ToolboxBitmap(typeof(AnalogClockControl), "AnalogClock.AnalogClockControl.bmp"), DefaultEvent("ValueChanged"), DefaultProperty("Value")]
|
|
public class AnalogClockControl : Control
|
|
{
|
|
#region Private Vars
|
|
|
|
private const float _BaseSize = 100.0f;
|
|
|
|
private Point _EditLoc;
|
|
private eClockEditStates _EditState;
|
|
private Size _LastSize;
|
|
private Timer _Timer;
|
|
#endregion
|
|
|
|
#region Implementation
|
|
/// <summary>
|
|
/// Default minimum size. Defaults to 100, 100.
|
|
/// </summary>
|
|
protected override Size DefaultMinimumSize
|
|
{
|
|
get { return new Size(100, 100); }
|
|
}
|
|
|
|
private bool _AntiAlias;
|
|
/// <summary>
|
|
/// Gets or sets whether anti-aliasing is used when rendering the control. Default value is true.
|
|
/// </summary>
|
|
[DefaultValue(true),
|
|
Category("Appearance"),
|
|
Description("Indicates whether Anti-aliasing is used when rendering the control.")]
|
|
public bool AntiAlias
|
|
{
|
|
get { return _AntiAlias; }
|
|
set
|
|
{
|
|
_AntiAlias = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool _AutomaticMode;
|
|
/// <summary>
|
|
/// Gets or sets the state for automatic mode. When true the clock will auto redraw once a second and display the current date/time. Default value is false.
|
|
/// </summary>
|
|
[DefaultValue(false),
|
|
Category("Behavior"),
|
|
Description("Toggles the state for automatic mode. When true the clock will auto redraw once a second and display the current date/time.")]
|
|
public bool AutomaticMode
|
|
{
|
|
get { return _AutomaticMode; }
|
|
set
|
|
{
|
|
_AutomaticMode = value;
|
|
if (_AutomaticMode)
|
|
{
|
|
_Value = DateTime.Now;
|
|
_IsEditable = false;
|
|
if (!DesignMode)
|
|
{
|
|
if (_Timer != null)
|
|
_Timer.Dispose();
|
|
_Timer = new Timer();
|
|
_Timer.Interval = 1000;
|
|
_Timer.Tick += Timer_Tick;
|
|
_Timer.Enabled = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_Timer != null)
|
|
{
|
|
_Timer.Enabled = false;
|
|
_Timer.Dispose();
|
|
_Timer = null;
|
|
}
|
|
}
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private eClockStyles _ClockStyle;
|
|
/// <summary>
|
|
/// Gets or sets clock style for this control.
|
|
/// </summary>
|
|
[DefaultValue(eClockStyles.Style1),
|
|
Category("Appearance"),
|
|
Description("The clock style for the control."),
|
|
RefreshProperties(RefreshProperties.All)]
|
|
public eClockStyles ClockStyle
|
|
{
|
|
get { return _ClockStyle; }
|
|
set
|
|
{
|
|
_ClockStyle = value;
|
|
if (_ClockStyle != eClockStyles.Custom)
|
|
{
|
|
if (_ClockStyleData != null)
|
|
{
|
|
_ClockStyleData.Parent = null;
|
|
_ClockStyleData.Dispose();
|
|
}
|
|
_ClockStyleData = new ClockStyleData(_ClockStyle, this);
|
|
}
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private ClockStyleData _ClockStyleData;
|
|
/// <summary>
|
|
/// Gets or sets the clock style data elements for this control.
|
|
/// </summary>
|
|
[Category("Appearance"),
|
|
Description("The clock style data for the control."),
|
|
RefreshProperties(RefreshProperties.All)]
|
|
public ClockStyleData ClockStyleData
|
|
{
|
|
get { return _ClockStyleData; }
|
|
set
|
|
{
|
|
if (_ClockStyleData != null) _ClockStyleData.Parent = null;
|
|
_ClockStyleData = value;
|
|
_ClockStyleData.Parent = this;
|
|
_ClockStyleData.Style = eClockStyles.Custom;
|
|
ClockStyle = eClockStyles.Custom;
|
|
Invalidate();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Resets the property to default value.
|
|
/// </summary>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public void ResetClockStyleData()
|
|
{
|
|
ClockStyle = eClockStyles.Style1;
|
|
}
|
|
|
|
private eClockIndicatorStyles _IndicatorStyle;
|
|
/// <summary>
|
|
/// Gets or sets a the indicator style the clock control. Default value is Ticks.
|
|
/// </summary>
|
|
[DefaultValue(eClockIndicatorStyles.Ticks),
|
|
Category("Appearance"),
|
|
Description("Indicates which indicator style the clock will use.")]
|
|
public eClockIndicatorStyles IndicatorStyle
|
|
{
|
|
get { return _IndicatorStyle; }
|
|
set
|
|
{
|
|
_IndicatorStyle = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool _IsEditable;
|
|
/// <summary>
|
|
/// Gets or sets whether the time can be changed by moving the clock hands. Default value is false.
|
|
/// </summary>
|
|
[DefaultValue(false),
|
|
Category("Behavior"),
|
|
Description("Indicates whether the time can be changed by moving the clock hands. If AutomaticMode is enabled, this setting is ignored.")]
|
|
public bool IsEditable
|
|
{
|
|
get { return _IsEditable; }
|
|
set
|
|
{
|
|
if (!AutomaticMode)
|
|
_IsEditable = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool _ShowGlassOverlay;
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to display the glass overlay on the clock control. Default value is true.
|
|
/// </summary>
|
|
[DefaultValue(true),
|
|
Category("Appearance"),
|
|
Description("Indicates whether the glass overlay on the clock control will be displayed or not.")]
|
|
public bool ShowGlassOverlay
|
|
{
|
|
get { return _ShowGlassOverlay; }
|
|
set
|
|
{
|
|
_ShowGlassOverlay = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private bool _ShowSecondHand;
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether to display the second hand on the clock control. Default value is true.
|
|
/// </summary>
|
|
[DefaultValue(true),
|
|
Category("Appearance"),
|
|
Description("Indicates whether the second hand on the clock control will be displayed or not.")]
|
|
public bool ShowSecondHand
|
|
{
|
|
get { return _ShowSecondHand; }
|
|
set
|
|
{
|
|
_ShowSecondHand = value;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs while user is dragging the mouse in order to change time.
|
|
/// </summary>
|
|
[Description("Occurs while user is dragging the mouse in order to change time.")]
|
|
public event TimeValueChangingEventHandler ValueChanging;
|
|
/// <summary>
|
|
/// Raises ValueChanging event.
|
|
/// </summary>
|
|
/// <param name="e">Provides event arguments.</param>
|
|
protected virtual void OnValueChanging(TimeValueChangingEventArgs e)
|
|
{
|
|
TimeValueChangingEventHandler handler = ValueChanging;
|
|
if (handler != null)
|
|
handler(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when Value i.e. time clock is displaying has changed.
|
|
/// </summary>
|
|
[Description("Occurs when Value i.e. time clock is displaying has changed.")]
|
|
public event EventHandler ValueChanged;
|
|
/// <summary>
|
|
/// Raises ValueChanged event.
|
|
/// </summary>
|
|
/// <param name="e">Provides event arguments.</param>
|
|
protected virtual void OnValueChanged(EventArgs e)
|
|
{
|
|
EventHandler handler = ValueChanged;
|
|
if (handler != null)
|
|
handler(this, e);
|
|
}
|
|
|
|
private DateTime _Value;
|
|
/// <summary>
|
|
/// Gets or sets the current date/time value for this control.
|
|
/// </summary>
|
|
[Category("Behavior"),
|
|
Description("The current date/time value for this control. If AutomaticMode is enabled, assigned values are ignored.")]
|
|
public DateTime Value
|
|
{
|
|
get { return _Value; }
|
|
set
|
|
{
|
|
_Value = value;
|
|
OnValueChanged(EventArgs.Empty);
|
|
Invalidate();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Resets the property to default value.
|
|
/// </summary>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public void ResetValue()
|
|
{
|
|
Value = DateTime.Now;
|
|
}
|
|
|
|
///// <summary>
|
|
///// Occurs when subproperty value has changed.
|
|
///// </summary>
|
|
//public event DevComponents.Schedule.Model.SubPropertyChangedEventHandler SubPropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the ClockControl class
|
|
/// </summary>
|
|
public AnalogClockControl()
|
|
{
|
|
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint |
|
|
ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw |
|
|
ControlStyles.SupportsTransparentBackColor, true);
|
|
|
|
_AntiAlias = true;
|
|
_AutomaticMode = false;
|
|
_ClockStyle = eClockStyles.Style1;
|
|
_ClockStyleData = new ClockStyleData();
|
|
_ClockStyleData.Parent = this;
|
|
_EditState = eClockEditStates.None;
|
|
_IndicatorStyle = eClockIndicatorStyles.Ticks;
|
|
_IsEditable = false;
|
|
_ShowGlassOverlay = true;
|
|
_ShowSecondHand = true;
|
|
_Value = DateTime.Now;
|
|
|
|
MinimumSize = new Size((int)_BaseSize, (int)_BaseSize); ;
|
|
Size = MinimumSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases all resources used by the class.
|
|
/// </summary>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if (disposing)
|
|
{
|
|
if (_ClockStyleData != null)
|
|
{
|
|
_ClockStyleData.Parent = null;
|
|
_ClockStyleData.Dispose();
|
|
}
|
|
|
|
if (_Timer != null)
|
|
{
|
|
_Timer.Dispose();
|
|
_Timer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
base.OnMouseDown(e);
|
|
|
|
RectangleF rect;
|
|
float bezelWidth, angle;
|
|
|
|
if (e.Button != MouseButtons.Left || !_IsEditable)
|
|
return;
|
|
|
|
bezelWidth = _ClockStyleData.BezelWidth * (ClientRectangle.Width - 1);
|
|
rect = new RectangleF(ClientRectangle.X + bezelWidth,
|
|
ClientRectangle.Y + bezelWidth,
|
|
(ClientRectangle.Width - 1) - bezelWidth * 2.0f,
|
|
(ClientRectangle.Height - 1) - bezelWidth * 2.0f);
|
|
|
|
angle = RoundToExactHour((float)_Value.TimeOfDay.TotalHours * 30.0f);
|
|
if (_ClockStyleData.HourHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
{
|
|
_LastMouseMoveHour = _Value.Hour;
|
|
_NewMouseMoveHour = _Value.Hour;
|
|
if (_Value.Hour > 12) _LastMouseMoveHour -= 12;
|
|
_EditState = eClockEditStates.Hour;
|
|
}
|
|
else
|
|
{
|
|
angle = (float)_Value.TimeOfDay.TotalMinutes * 6.0f;
|
|
if (_ClockStyleData.MinuteHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
_EditState = eClockEditStates.Minute;
|
|
else
|
|
{
|
|
angle = (float)_Value.TimeOfDay.Seconds * 6.0f;
|
|
if (_ShowSecondHand && _ClockStyleData.SecondHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
_EditState = eClockEditStates.Second;
|
|
}
|
|
}
|
|
if (_EditState != eClockEditStates.None)
|
|
_EditLoc = e.Location;
|
|
}
|
|
|
|
private int _LastMouseMoveHour = -1;
|
|
private int _NewMouseMoveHour = -1;
|
|
protected override void OnMouseMove(MouseEventArgs e)
|
|
{
|
|
base.OnMouseMove(e);
|
|
|
|
RectangleF rect;
|
|
float bezelWidth, angle;
|
|
|
|
if (!_IsEditable)
|
|
return;
|
|
|
|
if (_EditState != eClockEditStates.None)
|
|
{
|
|
_EditLoc = e.Location;
|
|
|
|
// We need to record the direction of hour hand movement so AM/PM can be switched
|
|
if (_EditState == eClockEditStates.Hour)
|
|
{
|
|
int hour = GetHourFromPoint(_EditLoc);
|
|
if (hour == 1 && _LastMouseMoveHour == 12)
|
|
_LastMouseMoveHour = 0;
|
|
else if (hour == 12 && _LastMouseMoveHour == 1)
|
|
_LastMouseMoveHour = 13;
|
|
_NewMouseMoveHour += (hour - _LastMouseMoveHour);
|
|
if (_NewMouseMoveHour < 0)
|
|
_NewMouseMoveHour = 23;
|
|
else if (_NewMouseMoveHour > 23)
|
|
_NewMouseMoveHour = 0;
|
|
_LastMouseMoveHour = hour;
|
|
|
|
}
|
|
|
|
OnValueChanging(new TimeValueChangingEventArgs(GetCurrentMouseTime()));
|
|
|
|
Invalidate();
|
|
}
|
|
else
|
|
{
|
|
bezelWidth = _ClockStyleData.BezelWidth * (ClientRectangle.Width - 1);
|
|
rect = new RectangleF(ClientRectangle.X + bezelWidth,
|
|
ClientRectangle.Y + bezelWidth,
|
|
(ClientRectangle.Width - 1) - bezelWidth * 2.0f,
|
|
(ClientRectangle.Height - 1) - bezelWidth * 2.0f);
|
|
|
|
angle = RoundToExactHour((float)_Value.TimeOfDay.TotalHours * 30.0f);
|
|
if (_ClockStyleData.HourHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
Cursor = Cursors.Hand;
|
|
else
|
|
{
|
|
angle = (float)_Value.TimeOfDay.TotalMinutes * 6.0f;
|
|
if (_ClockStyleData.MinuteHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
Cursor = Cursors.Hand;
|
|
else
|
|
{
|
|
angle = (float)_Value.TimeOfDay.Seconds * 6.0f;
|
|
if (_ShowSecondHand && _ClockStyleData.SecondHandStyle.ContainsPoint(rect, angle, e.Location))
|
|
Cursor = Cursors.Hand;
|
|
else if (Cursor != Cursors.Default)
|
|
Cursor = Cursors.Default;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private float GetClockAngleFromPoint(Point p)
|
|
{
|
|
float angle = (float)MathHelper.GetDegrees(GetAngleFromPoint(p));
|
|
angle += 90.0f;
|
|
while (angle < 0)
|
|
angle += 360;
|
|
while (angle >= 360)
|
|
angle -= 360;
|
|
return angle;
|
|
}
|
|
private int GetHourFromPoint(Point p)
|
|
{
|
|
float angle = RoundToExactHour(GetClockAngleFromPoint(p));
|
|
int hour = (int)Math.Round(angle / 30.0, 0);
|
|
if (hour == 0) hour = 12;
|
|
return hour;
|
|
}
|
|
private int GetMinuteFromPoint(Point p)
|
|
{
|
|
float angle = GetClockAngleFromPoint(p);
|
|
int minute = (int)Math.Round(angle / 6.0, 0);
|
|
if (minute == 60)
|
|
minute = 0;
|
|
return minute;
|
|
}
|
|
private int GetSecondFromPoint(Point p)
|
|
{
|
|
float angle = GetClockAngleFromPoint(p);
|
|
int minute = (int)Math.Round(angle / 6.0, 0);
|
|
if (minute == 60)
|
|
minute = 0;
|
|
return minute;
|
|
}
|
|
|
|
private DateTime GetCurrentMouseTime()
|
|
{
|
|
DateTime time = _Value;
|
|
switch (_EditState)
|
|
{
|
|
case eClockEditStates.Hour:
|
|
time = new DateTime(_Value.Year, _Value.Month, _Value.Day, _NewMouseMoveHour, _Value.Minute, _Value.Second);
|
|
break;
|
|
case eClockEditStates.Minute:
|
|
time = new DateTime(_Value.Year, _Value.Month, _Value.Day, _Value.Hour, GetMinuteFromPoint(_EditLoc), _Value.Second);
|
|
break;
|
|
case eClockEditStates.Second:
|
|
time = new DateTime(_Value.Year, _Value.Month, _Value.Day, _Value.Hour, _Value.Minute, GetSecondFromPoint(_EditLoc));
|
|
break;
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
protected override void OnMouseUp(MouseEventArgs e)
|
|
{
|
|
if (_EditState != eClockEditStates.None)
|
|
{
|
|
_EditLoc = e.Location;
|
|
Value = GetCurrentMouseTime();
|
|
_EditLoc = Point.Empty;
|
|
_EditState = eClockEditStates.None;
|
|
Cursor = Cursors.Default;
|
|
}
|
|
base.OnMouseUp(e);
|
|
}
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
base.OnPaint(e);
|
|
Graphics g;
|
|
RectangleF rect;
|
|
float bezelWidth;
|
|
|
|
g = e.Graphics;
|
|
|
|
TextRenderingHint textHint = g.TextRenderingHint;
|
|
SmoothingMode sm = g.SmoothingMode;
|
|
if (_AntiAlias)
|
|
{
|
|
g.TextRenderingHint = BarUtilities.AntiAliasTextRenderingHint;
|
|
g.SmoothingMode = SmoothingMode.AntiAlias;
|
|
}
|
|
|
|
bezelWidth = _ClockStyleData.BezelWidth * (ClientRectangle.Width - 1);
|
|
rect = new RectangleF(ClientRectangle.X + bezelWidth,
|
|
ClientRectangle.Y + bezelWidth,
|
|
(ClientRectangle.Width - 1) - bezelWidth * 2.0f,
|
|
(ClientRectangle.Height - 1) - bezelWidth * 2.0f);
|
|
|
|
|
|
e.Graphics.TranslateTransform(rect.X + rect.Width * 0.5f, rect.Y + rect.Height * 0.5f);
|
|
switch (_IndicatorStyle)
|
|
{
|
|
case eClockIndicatorStyles.Ticks:
|
|
DrawTicks(g, rect);
|
|
break;
|
|
case eClockIndicatorStyles.Numbers:
|
|
DrawNumbers(g, rect);
|
|
break;
|
|
}
|
|
|
|
DrawHands(g, rect, false);
|
|
DrawCap(g, rect);
|
|
DrawHands(g, rect, true);
|
|
|
|
if (_ShowGlassOverlay)
|
|
DrawGlassOverlay(g, rect);
|
|
|
|
g.TextRenderingHint = textHint;
|
|
g.SmoothingMode = sm;
|
|
}
|
|
|
|
protected override void OnPaintBackground(PaintEventArgs pevent)
|
|
{
|
|
base.OnPaintBackground(pevent);
|
|
Graphics gfx;
|
|
RectangleF innerRect, outerRect;
|
|
GraphicsPath outerPath, innerPath;
|
|
Brush brush;
|
|
Pen pen;
|
|
RectangleF imageRect = new RectangleF();
|
|
float aspect, bezelWidth;
|
|
float scaleFactor;
|
|
|
|
gfx = pevent.Graphics;
|
|
gfx.SmoothingMode = _AntiAlias ? SmoothingMode.AntiAlias : SmoothingMode.Default;
|
|
|
|
Rectangle clientRectangle = ClientRectangle;
|
|
if (_ClockStyleData.BezelColor.BorderWidth > 0)
|
|
{
|
|
int borderInflateSize = (int)((_ClockStyleData.BezelColor.BorderWidth * Math.Min(clientRectangle.Width, clientRectangle.Height)) / 2);
|
|
clientRectangle.Inflate(-borderInflateSize, -borderInflateSize);
|
|
}
|
|
|
|
scaleFactor = Math.Min(clientRectangle.Width, clientRectangle.Height);
|
|
bezelWidth = _ClockStyleData.BezelWidth * (scaleFactor - 1);
|
|
|
|
innerRect = new RectangleF(clientRectangle.X + bezelWidth,
|
|
clientRectangle.Y + bezelWidth,
|
|
(clientRectangle.Width - 1) - bezelWidth * 2.0f,
|
|
(clientRectangle.Height - 1) - bezelWidth * 2.0f);
|
|
outerRect = new RectangleF(clientRectangle.X,
|
|
clientRectangle.Y,
|
|
clientRectangle.Width - 1,
|
|
clientRectangle.Height - 1);
|
|
|
|
outerPath = new GraphicsPath();
|
|
innerPath = new GraphicsPath();
|
|
|
|
switch (_ClockStyleData.ClockShape)
|
|
{
|
|
case eClockShapes.Round:
|
|
outerPath.AddEllipse(outerRect);
|
|
outerPath.CloseAllFigures();
|
|
innerPath.AddEllipse(innerRect);
|
|
innerPath.CloseAllFigures();
|
|
break;
|
|
default:
|
|
outerPath.AddRectangle(outerRect);
|
|
innerPath.AddRectangle(innerRect);
|
|
break;
|
|
}
|
|
|
|
|
|
if (_ClockStyleData.FaceBackgroundImage != null)
|
|
{
|
|
gfx.SetClip(outerPath);
|
|
if (_ClockStyleData.FaceBackgroundImage.Width < _ClockStyleData.FaceBackgroundImage.Height)
|
|
aspect = (float)innerRect.Width / (float)_ClockStyleData.FaceBackgroundImage.Width;
|
|
else
|
|
aspect = (float)innerRect.Height / (float)_ClockStyleData.FaceBackgroundImage.Height;
|
|
imageRect.X = innerRect.X + ((innerRect.Width / 2.0f) - (aspect * _ClockStyleData.FaceBackgroundImage.Width) / 2.0f);
|
|
imageRect.Y = innerRect.Y + ((innerRect.Height / 2.0f) - (aspect * _ClockStyleData.FaceBackgroundImage.Height) / 2.0f);
|
|
imageRect.Width = aspect * _ClockStyleData.FaceBackgroundImage.Width;
|
|
imageRect.Height = aspect * _ClockStyleData.FaceBackgroundImage.Height;
|
|
gfx.DrawImage(_ClockStyleData.FaceBackgroundImage, imageRect);
|
|
|
|
}
|
|
|
|
gfx.ResetClip();
|
|
gfx.SetClip(innerPath, CombineMode.Exclude);
|
|
brush = _ClockStyleData.BezelColor.GetBrush(outerPath);
|
|
gfx.FillPath(brush, outerPath);
|
|
brush.Dispose();
|
|
if (_ClockStyleData.BezelColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.BezelColor.GetBorderPen(scaleFactor, PenAlignment.Center);
|
|
gfx.DrawPath(pen, outerPath);
|
|
pen.Dispose();
|
|
}
|
|
|
|
gfx.ResetClip();
|
|
if (_ClockStyleData.FaceBackgroundImage == null)
|
|
{
|
|
brush = _ClockStyleData.FaceColor.GetBrush(innerPath, new PointF(0.50f, 0.50f));
|
|
gfx.FillPath(brush, innerPath);
|
|
brush.Dispose();
|
|
}
|
|
if (_ClockStyleData.FaceColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.FaceColor.GetBorderPen(scaleFactor, PenAlignment.Center);
|
|
gfx.DrawPath(pen, innerPath);
|
|
pen.Dispose();
|
|
}
|
|
innerPath.Dispose();
|
|
outerPath.Dispose();
|
|
}
|
|
|
|
protected override void OnResize(EventArgs e)
|
|
{
|
|
base.OnResize(e);
|
|
//if (Width < 100)
|
|
//{
|
|
// Width = 100;
|
|
// Height = 100;
|
|
//}
|
|
//else
|
|
if (_LastSize.Width != Width)
|
|
Height = Width;
|
|
else
|
|
Width = Height;
|
|
|
|
_LastSize = Size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders the clock's center cap.
|
|
/// </summary>
|
|
/// <param name="gfx">Graphics object used for rendering.</param>
|
|
/// <param name="rect">Bounding rectangle.</param>
|
|
protected virtual void DrawCap(Graphics gfx, RectangleF rect)
|
|
{
|
|
float scaleFactor;
|
|
GraphicsPath path;
|
|
RectangleF capRect;
|
|
Pen pen;
|
|
Brush brush;
|
|
float capSize;
|
|
scaleFactor = Math.Min(rect.Width, rect.Height);
|
|
|
|
capSize = _ClockStyleData.CapSize * scaleFactor;
|
|
capRect = new RectangleF(-capSize / 2.0f, -capSize / 2.0f, capSize, capSize);
|
|
path = new GraphicsPath();
|
|
path.AddEllipse(capRect);
|
|
|
|
brush = _ClockStyleData.CapColor.GetBrush(path);
|
|
gfx.FillPath(brush, path);
|
|
if (_ClockStyleData.CapColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.CapColor.GetBorderPen(scaleFactor, PenAlignment.Outset);
|
|
gfx.DrawPath(pen, path);
|
|
pen.Dispose();
|
|
}
|
|
brush.Dispose();
|
|
path.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders the clock's glass overlay.
|
|
/// </summary>
|
|
/// <param name="gfx">Graphics object used for rendering.</param>
|
|
/// <param name="rect">Bounding rectangle.</param>
|
|
protected virtual void DrawGlassOverlay(Graphics gfx, RectangleF rect)
|
|
{
|
|
GraphicsState gState;
|
|
PathGradientBrush brush;
|
|
GraphicsPath path, brushPath;
|
|
PointF[] curvePoints;
|
|
float radius;
|
|
|
|
gState = gfx.Save();
|
|
rect.Width *= 0.95f;
|
|
rect.Height *= 0.95f;
|
|
rect.X = -rect.Width / 2.0f;
|
|
rect.Y = -rect.Height / 2.0f;
|
|
radius = Math.Min(rect.Width, rect.Height) / 2.0f;
|
|
|
|
brushPath = new GraphicsPath();
|
|
brushPath.AddEllipse(rect);
|
|
brush = new PathGradientBrush(brushPath);
|
|
brush.CenterPoint = new PointF(0.0f, 0.0f);
|
|
brush.CenterColor = Color.FromArgb(192, Color.White);
|
|
brush.SurroundColors = new Color[] { Color.FromArgb(64, Color.White) };
|
|
|
|
path = new GraphicsPath();
|
|
path.AddArc(rect, 180, 180);
|
|
curvePoints = new PointF[5];
|
|
curvePoints[0].X = -radius;
|
|
curvePoints[0].Y = 0.0f;
|
|
|
|
curvePoints[1].X = -radius * 0.5f;
|
|
curvePoints[1].Y = -radius * 0.175f;
|
|
|
|
curvePoints[2].X = 0.0f;
|
|
curvePoints[2].Y = -radius * 0.25f;
|
|
|
|
curvePoints[3].X = radius * 0.5f;
|
|
curvePoints[3].Y = -radius * 0.175f;
|
|
|
|
curvePoints[4].X = radius;
|
|
curvePoints[4].Y = 0.0f;
|
|
|
|
path.AddCurve(curvePoints);
|
|
path.CloseAllFigures();
|
|
|
|
gfx.RotateTransform(_ClockStyleData.GlassAngle);
|
|
gfx.FillPath(brush, path);
|
|
|
|
gfx.Restore(gState);
|
|
brush.Dispose();
|
|
brushPath.Dispose();
|
|
path.Dispose();
|
|
|
|
}
|
|
|
|
private static float RoundToExactHour(float angle)
|
|
{
|
|
return (float)Math.Floor((angle / 30f)) * 30f;
|
|
}
|
|
/// <summary>
|
|
/// Renders the clock's hands.
|
|
/// </summary>
|
|
/// <param name="gfx">Graphics object used for rendering.</param>
|
|
/// <param name="rect">Bounding rectangle.</param>
|
|
/// <param name="overCap">True if this is the rending pass after the cap has been rendered.</param>
|
|
protected virtual void DrawHands(Graphics gfx, RectangleF rect, bool overCap)
|
|
{
|
|
GraphicsState gState;
|
|
GraphicsPath path;
|
|
Pen pen;
|
|
Brush brush;
|
|
float scaleFactor, angle;
|
|
|
|
scaleFactor = Math.Min(rect.Width, rect.Height);
|
|
gState = gfx.Save();
|
|
gfx.ResetTransform();
|
|
|
|
//Hour Hand
|
|
if (_ClockStyleData.HourHandStyle.DrawOverCap == overCap)
|
|
{
|
|
if (_EditState == eClockEditStates.Hour)
|
|
angle = (float)MathHelper.GetDegrees(GetAngleFromPoint(_EditLoc)) + 90.0f;
|
|
else
|
|
angle = (float)(_Value.TimeOfDay.TotalHours > 12 ? _Value.TimeOfDay.TotalHours - 12 : _Value.TimeOfDay.TotalHours) * 30.0f;
|
|
|
|
// When clock is editable round up the angle to point at the hour exactly
|
|
if (_IsEditable && !_AutomaticMode)
|
|
{
|
|
angle = RoundToExactHour(angle);
|
|
}
|
|
path = _ClockStyleData.HourHandStyle.GenerateHandPath(rect, angle);
|
|
brush = _ClockStyleData.HourHandStyle.HandColor.GetBrush(path, _ClockStyleData.HourHandStyle.HandColor.BrushAngle + angle);
|
|
gfx.FillPath(brush, path);
|
|
if (_ClockStyleData.HourHandStyle.HandColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.HourHandStyle.HandColor.GetBorderPen(scaleFactor, PenAlignment.Outset);
|
|
gfx.DrawPath(pen, path);
|
|
pen.Dispose();
|
|
}
|
|
brush.Dispose();
|
|
path.Dispose();
|
|
}
|
|
|
|
//Minute Hand
|
|
if (_ClockStyleData.MinuteHandStyle.DrawOverCap == overCap)
|
|
{
|
|
if (_EditState == eClockEditStates.Minute)
|
|
angle = (float)MathHelper.GetDegrees(GetAngleFromPoint(_EditLoc)) + 90.0f;
|
|
else
|
|
angle = (float)_Value.TimeOfDay.TotalMinutes * 6.0f;
|
|
path = _ClockStyleData.MinuteHandStyle.GenerateHandPath(rect, angle);
|
|
brush = _ClockStyleData.MinuteHandStyle.HandColor.GetBrush(path, _ClockStyleData.MinuteHandStyle.HandColor.BrushAngle + angle);
|
|
gfx.FillPath(brush, path);
|
|
if (_ClockStyleData.MinuteHandStyle.HandColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.MinuteHandStyle.HandColor.GetBorderPen(scaleFactor, PenAlignment.Outset);
|
|
gfx.DrawPath(pen, path);
|
|
pen.Dispose();
|
|
}
|
|
brush.Dispose();
|
|
path.Dispose();
|
|
}
|
|
|
|
//Second Hand
|
|
if (_ShowSecondHand && _ClockStyleData.SecondHandStyle.DrawOverCap == overCap)
|
|
{
|
|
if (_EditState == eClockEditStates.Second)
|
|
angle = (float)MathHelper.GetDegrees(GetAngleFromPoint(_EditLoc)) + 90.0f;
|
|
else
|
|
angle = (float)_Value.TimeOfDay.Seconds * 6.0f;
|
|
path = _ClockStyleData.SecondHandStyle.GenerateHandPath(rect, angle);
|
|
brush = _ClockStyleData.SecondHandStyle.HandColor.GetBrush(path, _ClockStyleData.SecondHandStyle.HandColor.BrushAngle + angle);
|
|
gfx.FillPath(brush, path);
|
|
if (_ClockStyleData.SecondHandStyle.HandColor.BorderWidth > 0)
|
|
{
|
|
pen = _ClockStyleData.SecondHandStyle.HandColor.GetBorderPen(scaleFactor, PenAlignment.Outset);
|
|
gfx.DrawPath(pen, path);
|
|
pen.Dispose();
|
|
}
|
|
brush.Dispose();
|
|
path.Dispose();
|
|
}
|
|
gfx.Restore(gState);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders the clock's numeric hour indicators.
|
|
/// </summary>
|
|
/// <param name="gfx">Graphics object used for rendering.</param>
|
|
/// <param name="rect">Bounding rectangle.</param>
|
|
protected virtual void DrawNumbers(Graphics gfx, RectangleF rect)
|
|
{
|
|
PointF center, txtCenter;
|
|
float scaleFactor, radius, tickIncrement, angle;
|
|
Brush brush;
|
|
StringFormat strFormat;
|
|
Font fnt;
|
|
|
|
strFormat = new StringFormat();
|
|
strFormat.Alignment = StringAlignment.Center;
|
|
strFormat.LineAlignment = StringAlignment.Center;
|
|
|
|
scaleFactor = Math.Min(rect.Width, rect.Height) / _BaseSize;
|
|
fnt = new Font(_ClockStyleData.NumberFont.FontFamily, _ClockStyleData.NumberFont.Size * scaleFactor, _ClockStyleData.NumberFont.Style, GraphicsUnit.Pixel);
|
|
center = new PointF(rect.X + rect.Width / 2.0f, rect.Y + rect.Height / 2.0f);
|
|
radius = (rect.Width / 2.0f) - (gfx.MeasureString("12", fnt).Height) / 1.25f;
|
|
tickIncrement = (float)MathHelper.GetRadians(30.0f);
|
|
angle = (float)MathHelper.GetRadians(-60.0f);
|
|
brush = new SolidBrush(_ClockStyleData.NumberColor);
|
|
|
|
txtCenter = new PointF();
|
|
for (int i = 1; i <= 12; i++)
|
|
{
|
|
txtCenter.X = (float)(radius * Math.Cos(angle));
|
|
txtCenter.Y = (float)(radius * Math.Sin(angle));
|
|
gfx.DrawString(i.ToString(), fnt, brush, txtCenter, strFormat);
|
|
angle += tickIncrement;
|
|
}
|
|
|
|
brush.Dispose();
|
|
fnt.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Renders the clock's tick hour/minute indicators.
|
|
/// </summary>
|
|
/// <param name="gfx">Graphics object used for rendering.</param>
|
|
/// <param name="rect">Bounding rectangle.</param>
|
|
protected virtual void DrawTicks(Graphics gfx, RectangleF rect)
|
|
{
|
|
float scaleFactor;
|
|
PointF[] largePts, smallPts;
|
|
GraphicsState gState;
|
|
Brush largeBrush, smallBrush;
|
|
Pen largePen = null, smallPen = null;
|
|
GraphicsPath largePath, smallPath;
|
|
|
|
scaleFactor = Math.Min(rect.Width, rect.Height);
|
|
|
|
largePts = new PointF[4];
|
|
largePts[0].X = (-_ClockStyleData.LargeTickWidth * 0.5f) * scaleFactor;
|
|
largePts[0].Y = -0.45f * scaleFactor;
|
|
|
|
largePts[1].X = (-_ClockStyleData.LargeTickWidth * 0.5f) * scaleFactor;
|
|
largePts[1].Y = (-0.45f + _ClockStyleData.LargeTickLength) * scaleFactor;
|
|
|
|
largePts[2].X = (_ClockStyleData.LargeTickWidth * 0.5f) * scaleFactor;
|
|
largePts[2].Y = (-0.45f + _ClockStyleData.LargeTickLength) * scaleFactor;
|
|
|
|
largePts[3].X = (_ClockStyleData.LargeTickWidth * 0.5f) * scaleFactor;
|
|
largePts[3].Y = -0.45f * scaleFactor;
|
|
|
|
smallPts = new PointF[4];
|
|
smallPts[0].X = (-_ClockStyleData.SmallTickWidth * -0.5f) * scaleFactor;
|
|
smallPts[0].Y = -0.45f * scaleFactor;
|
|
|
|
smallPts[1].X = (-_ClockStyleData.SmallTickWidth * -0.5f) * scaleFactor;
|
|
smallPts[1].Y = (-0.45f + _ClockStyleData.SmallTickLength) * scaleFactor;
|
|
|
|
smallPts[2].X = (_ClockStyleData.SmallTickWidth * -0.5f) * scaleFactor;
|
|
smallPts[2].Y = (-0.45f + _ClockStyleData.SmallTickLength) * scaleFactor;
|
|
|
|
smallPts[3].X = (_ClockStyleData.SmallTickWidth * -0.5f) * scaleFactor;
|
|
smallPts[3].Y = -0.45f * scaleFactor;
|
|
|
|
largePath = new GraphicsPath();
|
|
largePath.AddPolygon(largePts);
|
|
|
|
smallPath = new GraphicsPath();
|
|
smallPath.AddPolygon(smallPts);
|
|
|
|
largeBrush = _ClockStyleData.LargeTickColor.GetBrush(largePath);
|
|
if (_ClockStyleData.LargeTickColor.BorderWidth > 0)
|
|
largePen = _ClockStyleData.LargeTickColor.GetBorderPen(scaleFactor, PenAlignment.Center);
|
|
|
|
smallBrush = _ClockStyleData.LargeTickColor.GetBrush(largePath);
|
|
if (_ClockStyleData.SmallTickColor.BorderWidth > 0)
|
|
smallPen = _ClockStyleData.SmallTickColor.GetBorderPen(scaleFactor, PenAlignment.Center);
|
|
|
|
gState = gfx.Save();
|
|
for (int i = 0; i < 12; i++)
|
|
{
|
|
gfx.FillPolygon(largeBrush, largePts);
|
|
if (largePen != null)
|
|
gfx.DrawPolygon(largePen, largePts);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
gfx.RotateTransform(6);
|
|
gfx.FillPolygon(smallBrush, smallPts);
|
|
if (smallPen != null)
|
|
gfx.DrawPolygon(smallPen, smallPts);
|
|
}
|
|
gfx.RotateTransform(6);
|
|
}
|
|
gfx.Restore(gState);
|
|
|
|
largePath.Dispose();
|
|
smallPath.Dispose();
|
|
largeBrush.Dispose();
|
|
smallBrush.Dispose();
|
|
if (largePen != null)
|
|
largePen.Dispose();
|
|
if (smallPen != null)
|
|
smallPen.Dispose();
|
|
}
|
|
|
|
private float GetAngleFromPoint(PointF pt)
|
|
{
|
|
float angle;
|
|
PointF center = new PointF(ClientRectangle.X + ClientRectangle.Width / 2.0f, ClientRectangle.Y + ClientRectangle.Height / 2.0f);
|
|
pt.X -= center.X;
|
|
pt.Y -= center.Y;
|
|
angle = (float)Math.Atan2((double)(pt.Y), (double)(pt.X));
|
|
return angle;
|
|
}
|
|
|
|
private void UpdateAutomaticTime()
|
|
{
|
|
if (_TimeZoneInfo != null)
|
|
Value = DevComponents.Schedule.TimeZoneInfo.ConvertTime(DateTime.Now, _TimeZoneInfo);
|
|
else
|
|
Value = DateTime.Now;
|
|
}
|
|
|
|
private void Timer_Tick(object sender, EventArgs e)
|
|
{
|
|
UpdateAutomaticTime();
|
|
}
|
|
|
|
private DevComponents.Schedule.TimeZoneInfo _TimeZoneInfo = null;
|
|
private string _TimeZone = string.Empty;
|
|
/// <summary>
|
|
/// Gets or sets the time-zone string identifier that is used to display the time when AutomaticMode=true and clock is displaying current time.
|
|
/// </summary>
|
|
[DefaultValue(""), Editor("DevComponents.DotNetBar.Design.TimeZoneSelectionEditor, DevComponents.DotNetBar.Design, Version=14.1.0.37, Culture=neutral, PublicKeyToken=90f470f34c89ccaf", typeof(System.Drawing.Design.UITypeEditor)), Category("Behavior"), Description("Indicates time-zone string identifier that is used to display the time when AutomaticMode=true and clock is displaying current time.")]
|
|
public string TimeZone
|
|
{
|
|
get { return _TimeZone; }
|
|
set
|
|
{
|
|
if (value != _TimeZone)
|
|
{
|
|
DevComponents.Schedule.TimeZoneInfo timeZoneInfo = null;
|
|
if (!string.IsNullOrEmpty(value))
|
|
timeZoneInfo = DevComponents.Schedule.TimeZoneInfo.FindSystemTimeZoneById(value);
|
|
if (timeZoneInfo != null || string.IsNullOrEmpty(value))
|
|
{
|
|
string oldValue = _TimeZone;
|
|
_TimeZone = value;
|
|
_TimeZoneInfo = timeZoneInfo;
|
|
OnTimeZoneChanged(oldValue, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Called when TimeZone property has changed.
|
|
/// </summary>
|
|
/// <param name="oldValue">Old property value</param>
|
|
/// <param name="newValue">New property value</param>
|
|
protected virtual void OnTimeZoneChanged(string oldValue, string newValue)
|
|
{
|
|
if (_AutomaticMode)
|
|
UpdateAutomaticTime();
|
|
//OnPropertyChanged(new PropertyChangedEventArgs("TimeZone"));
|
|
}
|
|
|
|
///// <summary>
|
|
///// Raises the SubPropertyChanged event.
|
|
///// </summary>
|
|
///// <param name="e">Event arguments</param>
|
|
//public void OnSubPropertyChanged(DevComponents.Schedule.Model.SubPropertyChangedEventArgs e)
|
|
//{
|
|
// if (SubPropertyChanged != null)
|
|
// SubPropertyChanged(this, e);
|
|
// Invalidate();
|
|
//}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumeration containing the available hour/minute indicators.
|
|
/// </summary>
|
|
public enum eClockIndicatorStyles
|
|
{
|
|
/// <summary>
|
|
/// Control will use ticks for hour/minute indicators.
|
|
/// </summary>
|
|
Ticks,
|
|
|
|
/// <summary>
|
|
/// Control will use numbers for hour indicators.
|
|
/// </summary>
|
|
Numbers
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumeration containing the available mouse edit states.
|
|
/// </summary>
|
|
public enum eClockEditStates
|
|
{
|
|
/// <summary>
|
|
/// Control is not currently in an edit state.
|
|
/// </summary>
|
|
None,
|
|
|
|
/// <summary>
|
|
/// Control is currently in an hour edit state.
|
|
/// </summary>
|
|
Hour,
|
|
|
|
/// <summary>
|
|
/// Control is currently in an minute edit state.
|
|
/// </summary>
|
|
Minute,
|
|
|
|
/// <summary>
|
|
/// Control is currently in an second edit state.
|
|
/// </summary>
|
|
Second
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides event arguments for TimeValueChanging event.
|
|
/// </summary>
|
|
public class TimeValueChangingEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Gets the current time represented by the control.
|
|
/// </summary>
|
|
public readonly DateTime Time;
|
|
/// <summary>
|
|
/// Initializes a new instance of the TimeValueChangingEventArgs class.
|
|
/// </summary>
|
|
/// <param name="time"></param>
|
|
public TimeValueChangingEventArgs(DateTime time)
|
|
{
|
|
Time = time;
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// Defines delegate for TimeValueChanging event.
|
|
/// </summary>
|
|
/// <param name="sender">Source of event.</param>
|
|
/// <param name="e">Event arguments</param>
|
|
public delegate void TimeValueChangingEventHandler(object sender, TimeValueChangingEventArgs e);
|
|
}
|