Skip to content

Commit

Permalink
Improved documentation and fixed some minor bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
dahall committed Nov 25, 2019
1 parent 8057b34 commit 4264357
Showing 1 changed file with 90 additions and 55 deletions.
145 changes: 90 additions & 55 deletions WIndows.Forms/Controls/TrackBarEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.Extensions;
using Vanara.PInvoke;
Expand All @@ -14,7 +13,7 @@

namespace Vanara.Windows.Forms
{
/// <summary>Extends the <see cref="TrackBar"/> class to provide full native-control functionality.</summary>
/// <summary>Extends the <see cref="TrackBar"/> class to provide full native-control functionality, including tick marks and value, and custom drawing.</summary>
/// <seealso cref="System.Windows.Forms.TrackBar"/>
public class TrackBarEx : TrackBar
{
Expand All @@ -34,13 +33,19 @@ public TrackBarEx()
requestedDim = PreferredDimension;
}

/// <summary>Occurs when the channel for a <see cref="TrackBarEx"/> needs to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.</summary>
/// <summary>
/// Occurs when the channel for a <see cref="TrackBarEx"/> needs to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.
/// </summary>
public event PaintEventHandler DrawChannel;

/// <summary>Occurs when the thumb for a <see cref="TrackBarEx"/> needs to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.</summary>
/// <summary>
/// Occurs when the thumb for a <see cref="TrackBarEx"/> needs to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.
/// </summary>
public event PaintEventHandler DrawThumb;

/// <summary>Occurs when the ticks for a <see cref="TrackBarEx"/> need to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.</summary>
/// <summary>
/// Occurs when the ticks for a <see cref="TrackBarEx"/> need to be drawn and the <see cref="OwnerDraw"/> property is set to <c>true</c>.
/// </summary>
public event PaintEventHandler DrawTics;

/// <summary>Gets or sets a value indicating whether to draw ticks based on the <see cref="TrackBar.TickFrequency"/> interval.</summary>
Expand Down Expand Up @@ -71,9 +76,7 @@ public Rectangle ChannelBounds
}
}

/// <summary>
/// Gets or sets a value indicating whether to limit thumb movement to the selection.
/// </summary>
/// <summary>Gets or sets a value indicating whether to limit thumb movement to the selection.</summary>
/// <value><c>true</c> if movement is limited to selection; otherwise, <c>false</c>.</value>
[DefaultValue(false), Category("Appearance"), Description("Indicates if thumb movement is limited to the selection range.")]
public bool LimitThumbToSelection
Expand Down Expand Up @@ -118,12 +121,18 @@ public bool LimitThumbToSelection
}

/// <summary>Gets or sets a value indicating whether the control is drawn by the operating system or by code that you provide.</summary>
/// <value><c>true</c> if the control is drawn by code that you provide; <c>false</c> if the control is drawn by the operating system. The default is <c>false</c>.</value>
/// <value>
/// <c>true</c> if the control is drawn by code that you provide; <c>false</c> if the control is drawn by the operating system. The
/// default is <c>false</c>.
/// </value>
[DefaultValue(false), Category("Behavior"), Description("Specifies whether to custom draw the trackbar.")]
public bool OwnerDraw { get; set; }

/// <summary>Gets or sets the upper limit of the selection range this TrackBar is working with.</summary>
/// <value>The logical position at which the selection ends. This value must be less than or equal to the value of the <see cref="TrackBar.Maximum"/> property.</value>
/// <value>
/// The logical position at which the selection ends. This value must be less than or equal to the value of the
/// <see cref="TrackBar.Maximum"/> property.
/// </value>
[DefaultValue(0), Category("Behavior"), Description("The ending logical position of the current selection range in a trackbar.")]
public int SelectionEnd
{
Expand All @@ -132,7 +141,10 @@ public int SelectionEnd
}

/// <summary>Gets or sets the lower limit of the selection range this TrackBar is working with.</summary>
/// <value>The logical position at which the selection starts. This value must be greater than or equal to the value of the <see cref="TrackBar.Minimum"/> property.</value>
/// <value>
/// The logical position at which the selection starts. This value must be greater than or equal to the value of the
/// <see cref="TrackBar.Minimum"/> property.
/// </value>
[DefaultValue(0), Category("Behavior"), Description("The starting logical position of the current selection range in a trackbar.")]
public int SelectionStart
{
Expand Down Expand Up @@ -169,7 +181,10 @@ public Rectangle ThumbBounds
}

/// <summary>Gets or sets the length of the thumb, overriding the default.</summary>
/// <value>The length of the thumb. This is the vertical length if the orientation is horizontal and the horizontal length if the orientation is vertical.</value>
/// <value>
/// The length of the thumb. This is the vertical length if the orientation is horizontal and the horizontal length if the
/// orientation is vertical.
/// </value>
[Category("Appearance"), Description("The length of the slider in a trackbar.")]
public int ThumbLength
{
Expand All @@ -183,31 +198,36 @@ public int ThumbLength
}

/// <summary>Gets or sets an array that contains the positions of the tick marks for a trackbar.</summary>
/// <value>The elements of the array specify the logical positions of the trackbar's tick marks, not including the first and last tick marks created by the trackbar. The logical positions can be any of the integer values in the trackbar's range of minimum to maximum slider positions.</value>
/// <value>
/// The elements of the array specify the logical positions of the trackbar's tick marks, not including the first and last tick
/// marks created by the trackbar. The logical positions can be any of the integer values in the trackbar's range of minimum to
/// maximum slider positions.
/// </value>
[DefaultValue(null), Category("Appearance"), Description("Indicates the logical values of the trackbar where ticks are drawn.")]
public int[] TickPositions
{
get
{
if (!IsHandleCreated || TickStyle == TickStyle.None) return null;
var tickCnt = SendMsg(TrackBarMessage.TBM_GETNUMTICS).ToInt32();
if (tickCnt == 0) return new int[0];
var ptr = SendMsg(TrackBarMessage.TBM_GETPTICS);
return ptr.ToIEnum<int>(SendMsg(TrackBarMessage.TBM_GETNUMTICS).ToInt32() - 2).OrderBy(i => i).Distinct().ToArray();
return ptr.ToIEnum<int>(tickCnt - 2).OrderBy(i => i).Distinct().ToArray();
}
set
{
if (value != null)
{
if (value.Min() < Minimum || value.Max() > Maximum) throw new ArgumentOutOfRangeException(nameof(TickPositions), "All values must be between Minimum and Maximum range values.");
if (TickStyle == TickStyle.None) throw new ArgumentException("Tick positions cannot be set when TickStyle is None.");
if (value.Min() < Minimum || value.Max() > Maximum) throw new ArgumentOutOfRangeException(nameof(TickPositions), "All values must be between Minimum and Maximum range values.");
}
if (ticks != null) SendMsg(TrackBarMessage.TBM_CLEARTICS);
ticks = value;
if (IsHandleCreated)
RecreateHandle();
}
}

/// <inheritdoc />
/// <inheritdoc/>
protected override CreateParams CreateParams
{
get
Expand All @@ -222,7 +242,7 @@ protected override CreateParams CreateParams
}
}

/// <inheritdoc />
/// <inheritdoc/>
protected override Size DefaultSize => new Size(104, PreferredDimension);

private int PreferredDimension
Expand All @@ -237,7 +257,10 @@ private int PreferredDimension
/// <summary>Sets the starting and ending positions for the available selection range in a trackbar.</summary>
/// <param name="rangeMin">The starting logical position for the selection range.</param>
/// <param name="rangeMax">The ending logical position for the selection range.</param>
/// <param name="redrawAll">If this parameter is TRUE, the trackbar is redrawn after the selection range is set. If this parameter is FALSE, the message sets the selection range but does not redraw the trackbar.</param>
/// <param name="redrawAll">
/// If this parameter is TRUE, the trackbar is redrawn after the selection range is set. If this parameter is FALSE, the message
/// sets the selection range but does not redraw the trackbar.
/// </param>
public void SetSelectionRange(int rangeMin, int rangeMax, bool redrawAll = true)
{
if (rangeMin == selMin && rangeMax == selMax) return;
Expand Down Expand Up @@ -279,50 +302,64 @@ protected virtual void AdjustSize()
}
}

/// <inheritdoc />
/// <inheritdoc/>
protected override void OnAutoSizeChanged(EventArgs e)
{
base.OnAutoSizeChanged(e);
AdjustSize();
}

/// <summary>Raises the <see cref="E:DrawChannel" /> event.</summary>
/// <summary>Raises the <see cref="E:DrawChannel"/> event.</summary>
/// <param name="pe">The <see cref="PaintEventArgs"/> instance containing the event data.</param>
/// <returns>If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system should not draw this item. If this method returns <c>false</c>, the system will draw the item.</returns>
/// <returns>
/// If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system
/// should not draw this item. If this method returns <c>false</c>, the system will draw the item.
/// </returns>
protected virtual bool OnDrawChannel(PaintEventArgs pe)
{
DrawChannel?.Invoke(this, pe);
return DrawChannel != null;
}

/// <summary>Raises the <see cref="E:DrawThumb" /> event.</summary>
/// <summary>Raises the <see cref="E:DrawThumb"/> event.</summary>
/// <param name="pe">The <see cref="PaintEventArgs"/> instance containing the event data.</param>
/// <returns>If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system should not draw this item. If this method returns <c>false</c>, the system will draw the item.</returns>
/// <returns>
/// If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system
/// should not draw this item. If this method returns <c>false</c>, the system will draw the item.
/// </returns>
protected virtual bool OnDrawThumb(PaintEventArgs pe)
{
DrawThumb?.Invoke(this, pe);
return DrawThumb != null;
}

/// <summary>Raises the <see cref="E:DrawTics" /> event.</summary>
/// <summary>Raises the <see cref="E:DrawTics"/> event.</summary>
/// <param name="pe">The <see cref="PaintEventArgs"/> instance containing the event data.</param>
/// <returns>If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system should not draw this item. If this method returns <c>false</c>, the system will draw the item.</returns>
/// <returns>
/// If overwritten, the method should return <c>true</c> to indicate that all drawing has been done by the method and the system
/// should not draw this item. If this method returns <c>false</c>, the system will draw the item.
/// </returns>
protected virtual bool OnDrawTics(PaintEventArgs pe)
{
DrawTics?.Invoke(this, pe);
return DrawTics != null;
}

/// <inheritdoc />
/// <inheritdoc/>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (showSel) SetSelectionRange(selMin, selMax);
if (thumbLength >= 0) SendMsg(TrackBarMessage.TBM_SETTHUMBLENGTH, thumbLength);
if (ticks != null && TickStyle != TickStyle.None) SetTicks();
SendMsg(TrackBarMessage.TBM_CLEARTICS);
if (ticks != null && TickStyle != TickStyle.None)
foreach (var t in ticks)
SendMsg(TrackBarMessage.TBM_SETTIC, 0, t);
AdjustSize();
}

/// <summary>Raises the <see cref="Control.MouseWheel"/> event.</summary>
/// <param name="e">A <see cref="MouseEventArgs"/> that contains the event data.</param>
protected override void OnMouseWheel(MouseEventArgs e)
{
const int WHEEL_DELTA = 120;
Expand All @@ -332,46 +369,51 @@ protected override void OnMouseWheel(MouseEventArgs e)
if (hme.Handled) return;
hme.Handled = true;
}

if ((ModifierKeys & (Keys.Shift | Keys.Alt)) != 0 || MouseButtons != MouseButtons.None)
return; // Do not scroll when Shift or Alt key is down, or when a mouse button is down.

var wheelScrollLines = SystemInformation.MouseWheelScrollLines;
if (wheelScrollLines == 0) return; // Do not scroll when the user system setting is 0 lines per notch

Debug.Assert(cumulativeWheelData > -WHEEL_DELTA, "cumulativeWheelData is too small");
Debug.Assert(cumulativeWheelData < WHEEL_DELTA, "cumulativeWheelData is too big");
cumulativeWheelData += e.Delta;

var partialNotches = cumulativeWheelData / (float)WHEEL_DELTA;

if (wheelScrollLines == -1)
wheelScrollLines = TickFrequency;

// Evaluate number of bands to scroll
var scrollBands = (int)(wheelScrollLines * partialNotches);

if (scrollBands != 0)
{
int absScrollBands;
if (scrollBands > 0) {
if (scrollBands > 0)
{
absScrollBands = scrollBands;
Value = Math.Min(absScrollBands+Value, showSel && limitThumbToSel ? selMax : Maximum);
Value = Math.Min(absScrollBands + Value, showSel && limitThumbToSel ? selMax : Maximum);
cumulativeWheelData -= (int)(scrollBands * (WHEEL_DELTA / (float)wheelScrollLines));
}
else {
else
{
absScrollBands = -scrollBands;
Value = Math.Max(Value-absScrollBands, showSel && limitThumbToSel ? selMin : Minimum);
Value = Math.Max(Value - absScrollBands, showSel && limitThumbToSel ? selMin : Minimum);
cumulativeWheelData -= (int)(scrollBands * (WHEEL_DELTA / (float)wheelScrollLines));
}
}

if (e.Delta != Value) {

if (e.Delta != Value)
{
OnScroll(EventArgs.Empty);
OnValueChanged(EventArgs.Empty);
}
}

/// <summary>Raises the <see cref="E:System.Windows.Forms.TrackBar.ValueChanged"/> event.</summary>
/// <param name="e">The <see cref="T:System.EventArgs"/> that contains the event data.</param>
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
Expand All @@ -386,7 +428,7 @@ protected override void OnValueChanged(EventArgs e)
/// <returns>The result value defined for the message.</returns>
protected IntPtr SendMsg(TrackBarMessage msg, int wParam = 0, int lParam = 0) => IsHandleCreated ? SendMessage(Handle, (uint)msg, (IntPtr)wParam, (IntPtr)lParam) : IntPtr.Zero;

/// <inheritdoc />
/// <inheritdoc/>
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
requestedDim = Orientation == Orientation.Horizontal ? height : width;
Expand All @@ -408,21 +450,21 @@ protected override void SetBoundsCore(int x, int y, int width, int height, Bound
typeof(Control).GetMethod("SetBoundsCore", BindingFlags.Instance | BindingFlags.NonPublic).InvokeNotOverride(this, x, y, width, height, specified);
}

/// <inheritdoc />
/// <inheritdoc/>
protected override void WndProc(ref Message m)
{
var msg = (WindowMessage) m.Msg;
var msg = (WindowMessage)m.Msg;
//Debug.WriteLine($"TB WndProc: Msg={msg}");
if (msg == (WindowMessage.WM_NOTIFY | WindowMessage.WM_REFLECT))
{
if (OwnerDraw)
{
var hdr = (NMHDR)m.GetLParam(typeof(NMHDR));
if (hdr.code == (int) CommonControlNotification.NM_CUSTOMDRAW)
if (hdr.code == (int)CommonControlNotification.NM_CUSTOMDRAW)
{
var cd = (NMCUSTOMDRAW) m.GetLParam(typeof(NMCUSTOMDRAW));
Debug.WriteLine($"{new TimeSpan(Environment.TickCount)} TBCustDraw: {cd.dwDrawStage}, {cd.dwItemSpec.ToInt32()}, {cd.uItemState}, {(Rectangle) cd.rc}");
m.Result = new IntPtr((int) CustomDraw(ref cd));
var cd = (NMCUSTOMDRAW)m.GetLParam(typeof(NMCUSTOMDRAW));
Debug.WriteLine($"{new TimeSpan(Environment.TickCount)} TBCustDraw: {cd.dwDrawStage}, {cd.dwItemSpec.ToInt32()}, {cd.uItemState}, {(Rectangle)cd.rc}");
m.Result = new IntPtr((int)CustomDraw(ref cd));
return;
}
}
Expand Down Expand Up @@ -495,15 +537,8 @@ private void ResetThumbLength()

private IntPtr SendMsg(TrackBarMessage msg, ref RECT rect) => SendMessage(Handle, (uint)msg, IntPtr.Zero, ref rect);

private void SetTicks()
{
if (ticks == null) return;
foreach (var t in ticks)
SendMsg(TrackBarMessage.TBM_SETTIC, 0, t);
}

private bool ShouldSerializeThumbLength() => thumbLength >= 0;

private bool ShouldSerializeTickPositions() => ticks != null;
private bool ShouldSerializeTickPositions() => ticks != null && ticks.Length > 0;
}
}

0 comments on commit 4264357

Please sign in to comment.