using System.ComponentModel; using System.Windows.Forms.Design; using System.Windows.Forms; using System.Drawing; using System.Windows.Forms.VisualStyles; using System; [Designer(typeof(DoubleTrackBar.DoubleTrackBarDesigner))] public class DoubleTrackBar : Control { public DoubleTrackBar() { this.DoubleBuffered = true; this.SetDefaults(); } private void SetDefaults() { this.Orientation = Orientation.Horizontal; this.SmallChange = 1; this.Maximum = 10; this.Minimum = 0; this.ValueLeft = 0; this.ValueRight = 7; } #region Private Fields private TrackBarThumbState leftThumbState; private TrackBarThumbState rightThumbState; private bool draggingLeft; private bool draggingRight; #endregion #region Enums public enum Thumbs { None = 0, Left = 1, Right = 2 } #endregion #region Properties private Thumbs _SelectedThumb; /// /// Gets the thumb that had focus last. /// /// The thumb that had focus last. [Description("The thumb that had focus last.")] public Thumbs SelectedThumb { get { return _SelectedThumb; } private set { _SelectedThumb = value; } } private int _ValueLeft; /// /// Gets or sets the position of the left slider. /// /// The position of the left slider. [Description("The position of the left slider.")] public int ValueLeft { get { return _ValueLeft; } set { if (value < this.Minimum || value > this.Maximum) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'ValueLeft'. 'ValueLeft' should be between 'Minimum' and 'Maximum'.", value.ToString()), "ValueLeft"); } if (value > this.ValueRight) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'ValueLeft'. 'ValueLeft' should be less than or equal to 'ValueRight'.", value.ToString()), "ValueLeft"); } _ValueLeft = value; this.OnValueChanged(EventArgs.Empty); this.OnLeftValueChanged(EventArgs.Empty); this.Invalidate(); } } private int _ValueRight; /// /// Gets or sets the position of the right slider. /// /// The position of the right slider. [Description("The position of the right slider.")] public int ValueRight { get { return _ValueRight; } set { if (value < this.Minimum || value > this.Maximum) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'ValueRight'. 'ValueRight' should be between 'Minimum' and 'Maximum'.", value.ToString()), "ValueRight"); } if (value < this.ValueLeft) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'ValueRight'. 'ValueRight' should be greater than or equal to 'ValueLeft'.", value.ToString()), "ValueLeft"); } _ValueRight = value; this.OnValueChanged(EventArgs.Empty); this.OnRightValueChanged(EventArgs.Empty); this.Invalidate(); } } private int _Minimum; /// /// Gets or sets the minimum value. /// /// The minimum value. [Description("The minimum value.")] public int Minimum { get { return _Minimum; } set { if (value >= this.Maximum) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'Minimum'. 'Minimum' should be less than 'Maximum'.", value.ToString()), "Minimum"); } _Minimum = value; this.Invalidate(); } } private int _Maximum; /// /// Gets or sets the maximum value. /// /// The maximum value. [Description("The maximum value.")] public int Maximum { get { return _Maximum; } set { if (value <= this.Minimum) { throw new ArgumentException(string.Format("Value of '{0}' is not valid for 'Maximum'. 'Maximum' should be greater than 'Minimum'.", value.ToString()), "Maximum"); } _Maximum = value; this.Invalidate(); } } private Orientation _Orientation; /// /// Gets or sets the orientation of the control. /// /// The orientation of the control. [Description("The orientation of the control.")] private Orientation Orientation { get { return _Orientation; } set { _Orientation = value; } } private int _SmallChange; /// /// Gets or sets the amount of positions the closest slider moves when the control is clicked. /// /// The amount of positions the closest slider moves when the control is clicked. [Description("The amount of positions the closest slider moves when the control is clicked.")] public int SmallChange { get { return _SmallChange; } set { _SmallChange = value; } } private double RelativeValueLeft { get { var diff = this.Maximum - this.Minimum; return ((diff == 0) ? this.ValueLeft : this.ValueLeft / (double)diff); } } private double RelativeValueRight { get { var diff = this.Maximum - this.Minimum; return ((diff == 0) ? this.ValueLeft : this.ValueRight / (double)diff); } } #endregion #region Methods public void IncrementLeft() { var newValue = Math.Min(this.ValueLeft + 1, this.Maximum); if (this.IsValidValueLeft(newValue)) { this.ValueLeft = newValue; } this.Invalidate(); } public void IncrementRight() { var newValue = Math.Min(this.ValueRight + 1, this.Maximum); if (this.IsValidValueRight(newValue)) { this.ValueRight = newValue; } this.Invalidate(); } public void DecrementLeft() { var newValue = Math.Max(this.ValueLeft - 1, this.Minimum); if (this.IsValidValueLeft(newValue)) { this.ValueLeft = newValue; } this.Invalidate(); } public void DecrementRight() { var newValue = Math.Max(this.ValueRight - 1, this.Minimum); if (this.IsValidValueRight(newValue)) { this.ValueRight = newValue; } this.Invalidate(); } private bool IsValidValueLeft(int value) { return (value >= this.Minimum && value <= this.Maximum && value < this.ValueRight); } private bool IsValidValueRight(int value) { return (value >= this.Minimum && value <= this.Maximum && value > this.ValueLeft); } private Rectangle GetLeftThumbRectangle(Graphics g = null) { var shouldDispose = (g == null); if (shouldDispose) { g = this.CreateGraphics(); } var rect = this.GetThumbRectangle(this.RelativeValueLeft, g); if (shouldDispose) { g.Dispose(); } return rect; } private Rectangle GetRightThumbRectangle(Graphics g = null) { var shouldDispose = (g == null); if (shouldDispose) { g = this.CreateGraphics(); } var rect = this.GetThumbRectangle(this.RelativeValueRight, g); if (shouldDispose) { g.Dispose(); } return rect; } private Rectangle GetThumbRectangle(double relativeValue, Graphics g) { var size = TrackBarRenderer.GetBottomPointingThumbSize(g, TrackBarThumbState.Normal); var border = Convert.ToInt32(size.Width / 2.0); var w = this.GetTrackRectangle(border).Width; var x = Convert.ToInt32(Math.Abs(this.Minimum) / (this.Maximum - this.Minimum) * w + relativeValue * w); var y = Convert.ToInt32((this.Height - size.Height) / 2.0); return new Rectangle(new Point(x, y), size); } private Rectangle GetTrackRectangle(int border) { //TODO: Select Case for hor/ver return new Rectangle(border, Convert.ToInt32(this.Height / 2.0) - 3, this.Width - 2 * border - 1, 4); } private Thumbs GetClosestSlider(Point point) { var leftThumbRect = this.GetLeftThumbRectangle(); var rightThumbRect = this.GetRightThumbRectangle(); if (this.Orientation == Orientation.Horizontal) { if (Math.Abs(leftThumbRect.X - point.X) > Math.Abs(rightThumbRect.X - point.X) && Math.Abs(leftThumbRect.Right - point.X) > Math.Abs(rightThumbRect.Right - point.X)) { return Thumbs.Right; } else { return Thumbs.Left; } } else { if (Math.Abs(leftThumbRect.Y - point.Y) > Math.Abs(rightThumbRect.Y - point.Y) && Math.Abs(leftThumbRect.Bottom - point.Y) > Math.Abs(rightThumbRect.Bottom - point.Y)) { return Thumbs.Right; } else { return Thumbs.Left; } } } private void SetThumbState(Point location, TrackBarThumbState newState) { var leftThumbRect = this.GetLeftThumbRectangle(); var rightThumbRect = this.GetRightThumbRectangle(); if (leftThumbRect.Contains(location)) { leftThumbState = newState; } else { if (this.SelectedThumb == Thumbs.Left) { leftThumbState = TrackBarThumbState.Hot; } else { leftThumbState = TrackBarThumbState.Normal; } } if (rightThumbRect.Contains(location)) { rightThumbState = newState; } else { if (this.SelectedThumb == Thumbs.Right) { rightThumbState = TrackBarThumbState.Hot; } else { rightThumbState = TrackBarThumbState.Normal; } } } protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) { base.OnMouseMove(e); this.SetThumbState(e.Location, TrackBarThumbState.Hot); var offset = Convert.ToInt32(e.Location.X / (this.Width) * (this.Maximum - this.Minimum)); var newValue = this.Minimum + offset; if (draggingLeft) { if (this.IsValidValueLeft(newValue)) { this.ValueLeft = newValue; } } else if (draggingRight) { if (this.IsValidValueRight(newValue)) { this.ValueRight = newValue; } } this.Invalidate(); } protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) { base.OnMouseDown(e); this.Focus(); this.SetThumbState(e.Location, TrackBarThumbState.Pressed); draggingLeft = (leftThumbState == TrackBarThumbState.Pressed); if (!draggingLeft) { draggingRight = (rightThumbState == TrackBarThumbState.Pressed); } if (draggingLeft) { this.SelectedThumb = Thumbs.Left; } else if (draggingRight) { this.SelectedThumb = Thumbs.Right; } if (!draggingLeft && !draggingRight) { if (this.GetClosestSlider(e.Location) == Thumbs.Left) { if (e.X < this.GetLeftThumbRectangle().X) { this.DecrementLeft(); } else { this.IncrementLeft(); } this.SelectedThumb = Thumbs.Left; } else { if (e.X < this.GetRightThumbRectangle().X) { this.DecrementRight(); } else { this.IncrementRight(); } this.SelectedThumb = Thumbs.Right; } } this.Invalidate(); } protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) { base.OnMouseUp(e); draggingLeft = false; draggingRight = false; this.Invalidate(); } protected override void OnMouseWheel(System.Windows.Forms.MouseEventArgs e) { base.OnMouseWheel(e); if (e.Delta == 0) { return; } if (this.SelectedThumb == Thumbs.Left) { if (e.Delta > 0) { this.IncrementLeft(); } else { this.DecrementLeft(); } } else if (this.SelectedThumb == Thumbs.Right) { if (e.Delta > 0) { this.IncrementRight(); } else { this.DecrementRight(); } } } protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) { base.OnPaint(e); var thumbSize = this.GetThumbRectangle(0, e.Graphics).Size; var trackRect = this.GetTrackRectangle(Convert.ToInt32(thumbSize.Width / 2.0)); var ticksRect = trackRect; ticksRect.Offset(0, 15); TrackBarRenderer.DrawVerticalTrack(e.Graphics, trackRect); TrackBarRenderer.DrawHorizontalTicks(e.Graphics, ticksRect, this.Maximum - this.Minimum + 1, EdgeStyle.Etched); TrackBarRenderer.DrawBottomPointingThumb(e.Graphics, this.GetLeftThumbRectangle(e.Graphics), leftThumbState); TrackBarRenderer.DrawBottomPointingThumb(e.Graphics, this.GetRightThumbRectangle(e.Graphics), rightThumbState); } #endregion #region Events public event EventHandler ValueChanged; public event EventHandler LeftValueChanged; public event EventHandler RightValueChanged; protected virtual void OnValueChanged(EventArgs e) { if (ValueChanged != null) ValueChanged(this, e); } protected virtual void OnLeftValueChanged(EventArgs e) { if (LeftValueChanged != null) LeftValueChanged(this, e); } protected virtual void OnRightValueChanged(EventArgs e) { if (RightValueChanged != null) RightValueChanged(this, e); } #endregion #region Designer internal class DoubleTrackBarDesigner : ControlDesigner { private DoubleTrackBar TrackBar { get { return (DoubleTrackBar)this.Control; } } protected override bool GetHitTest(System.Drawing.Point point) { var pt = this.TrackBar.PointToClient(point); if (this.TrackBar.GetLeftThumbRectangle().Contains(pt) || this.TrackBar.GetRightThumbRectangle().Contains(pt)) { return true; } return base.GetHitTest(point); } } #endregion }