namespace CustomScrollBar { using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; /// /// A custom scrollbar control. /// [Designer(typeof(Design.ScrollBarControlDesigner))] [DefaultEvent("Scroll")] [DefaultProperty("Value")] public class ScrollBarEx : Control { #region fields /// /// Redraw const. /// private const int SETREDRAW = 11; /// /// Indicates many changes to the scrollbar are happening, so stop painting till finished. /// private bool inUpdate; /// /// The scrollbar orientation - horizontal / vertical. /// private ScrollBarOrientation orientation = ScrollBarOrientation.Vertical; /// /// The scroll orientation in scroll events. /// private ScrollOrientation scrollOrientation = ScrollOrientation.VerticalScroll; /// /// The clicked channel rectangle. /// private Rectangle clickedBarRectangle; /// /// The thumb rectangle. /// private Rectangle thumbRectangle; /// /// The top arrow rectangle. /// private Rectangle topArrowRectangle; /// /// The bottom arrow rectangle. /// private Rectangle bottomArrowRectangle; /// /// The channel rectangle. /// private Rectangle channelRectangle; /// /// Indicates if top arrow was clicked. /// private bool topArrowClicked; /// /// Indicates if bottom arrow was clicked. /// private bool bottomArrowClicked; /// /// Indicates if channel rectangle above the thumb was clicked. /// private bool topBarClicked; /// /// Indicates if channel rectangle under the thumb was clicked. /// private bool bottomBarClicked; /// /// Indicates if the thumb was clicked. /// private bool thumbClicked; /// /// The state of the thumb. /// private ScrollBarState thumbState = ScrollBarState.Normal; /// /// The state of the top arrow. /// private ScrollBarArrowButtonState topButtonState = ScrollBarArrowButtonState.UpNormal; /// /// The state of the bottom arrow. /// private ScrollBarArrowButtonState bottomButtonState = ScrollBarArrowButtonState.DownNormal; /// /// The scrollbar value minimum. /// private int minimum; /// /// The scrollbar value maximum. /// private int maximum = 100; /// /// The small change value. /// private int smallChange = 1; /// /// The large change value. /// private int largeChange = 10; /// /// The value of the scrollbar. /// private int value; /// /// The width of the thumb. /// private int thumbWidth = 15; /// /// The height of the thumb. /// private int thumbHeight; /// /// The width of an arrow. /// private int arrowWidth = 15; /// /// The height of an arrow. /// private int arrowHeight = 17; /// /// The bottom limit for the thumb bottom. /// private int thumbBottomLimitBottom; /// /// The bottom limit for the thumb top. /// private int thumbBottomLimitTop; /// /// The top limit for the thumb top. /// private int thumbTopLimit; /// /// The current position of the thumb. /// private int thumbPosition; /// /// The track position. /// private int trackPosition; /// /// The progress timer for moving the thumb. /// private Timer progressTimer = new Timer(); /// /// The border color. /// private Color borderColor = Color.FromArgb(93, 140, 201); /// /// The border color in disabled state. /// private Color disabledBorderColor = Color.Gray; /// /// The list of background markers. /// private List backgroundmarkers; /// /// The color of background markers. /// private Color backgroundmarkercolor = Color.DarkGray; #region context menu items /// /// Context menu strip. /// private ContextMenuStrip contextMenu; /// /// Container for components. /// private IContainer components; /// /// Menu item. /// private ToolStripMenuItem tsmiScrollHere; /// /// Menu separator. /// private ToolStripSeparator toolStripSeparator1; /// /// Menu item. /// private ToolStripMenuItem tsmiTop; /// /// Menu item. /// private ToolStripMenuItem tsmiBottom; /// /// Menu separator. /// private ToolStripSeparator toolStripSeparator2; /// /// Menu item. /// private ToolStripMenuItem tsmiLargeUp; /// /// Menu item. /// private ToolStripMenuItem tsmiLargeDown; /// /// Menu separator. /// private ToolStripSeparator toolStripSeparator3; /// /// Menu item. /// private ToolStripMenuItem tsmiSmallUp; /// /// Menu item. /// private ToolStripMenuItem tsmiSmallDown; #endregion #endregion #region constructor /// /// Initializes a new instance of the class. /// public ScrollBarEx() { // sets the control styles of the control SetStyle( ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.Selectable | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); // initializes the context menu this.InitializeComponent(); this.Width = 19; this.Height = 200; // sets the scrollbar up this.SetUpScrollBar(); // timer for clicking and holding the mouse button // over/below the thumb and on the arrow buttons this.progressTimer.Interval = 20; this.progressTimer.Tick += this.ProgressTimerTick; // no image margin in context menu this.contextMenu.ShowImageMargin = false; this.ContextMenuStrip = this.contextMenu; } #endregion #region events /// /// Occurs when the scrollbar scrolled. /// [Category("Behavior")] [Description("Is raised, when the scrollbar was scrolled.")] public event ScrollEventHandler Scroll; #endregion #region properties /// /// Gets or sets the orientation. /// [Category("Layout")] [Description("Gets or sets the orientation.")] [DefaultValue(ScrollBarOrientation.Vertical)] public ScrollBarOrientation Orientation { get { return this.orientation; } set { // no change - return if (value == this.orientation) { return; } this.orientation = value; // change text of context menu entries this.ChangeContextMenuItems(); // save scroll orientation for scroll event this.scrollOrientation = value == ScrollBarOrientation.Vertical ? ScrollOrientation.VerticalScroll : ScrollOrientation.HorizontalScroll; // only in DesignMode switch width and height if (this.DesignMode) { this.Size = new Size(this.Height, this.Width); } // sets the scrollbar up this.SetUpScrollBar(); } } /// /// Gets or sets the minimum value. /// [Category("Behavior")] [Description("Gets or sets the minimum value.")] [DefaultValue(0)] public int Minimum { get { return this.minimum; } set { // no change or value invalid - return if (this.minimum == value || value < 0 || value >= this.maximum) { return; } this.minimum = value; // current value less than new minimum value - adjust if (this.value < value) { this.value = value; } // is current large change value invalid - adjust if (this.largeChange > this.maximum - this.minimum) { this.largeChange = this.maximum - this.minimum; } this.SetUpScrollBar(); // current value less than new minimum value - adjust if (this.value < value) { this.Value = value; } else { // current value is valid - adjust thumb position this.ChangeThumbPosition(this.GetThumbPosition()); this.Refresh(); } } } /// /// Gets or sets the maximum value. /// [Category("Behavior")] [Description("Gets or sets the maximum value.")] [DefaultValue(100)] public int Maximum { get { return this.maximum; } set { // no change or new max. value invalid - return if (value == this.maximum || value < 1 || value <= this.minimum) { return; } this.maximum = value; // is large change value invalid - adjust if (this.largeChange > this.maximum - this.minimum) { this.largeChange = this.maximum - this.minimum; } this.SetUpScrollBar(); // is current value greater than new maximum value - adjust if (this.value > value) { this.Value = this.maximum; } else { // current value is valid - adjust thumb position this.ChangeThumbPosition(this.GetThumbPosition()); this.Refresh(); } } } /// /// Gets or sets the small change amount. /// [Category("Behavior")] [Description("Gets or sets the small change value.")] [DefaultValue(1)] public int SmallChange { get { return this.smallChange; } set { // no change or new small change value invalid - return if (value == this.smallChange || value < 1 || value >= this.largeChange) { return; } this.smallChange = value; this.SetUpScrollBar(); } } /// /// Gets or sets the large change amount. /// [Category("Behavior")] [Description("Gets or sets the large change value.")] [DefaultValue(10)] public int LargeChange { get { return this.largeChange; } set { // no change or new large change value is invalid - return if (value == this.largeChange || value < this.smallChange || value < 2) { return; } // if value is greater than scroll area - adjust if (value > this.maximum - this.minimum) { this.largeChange = this.maximum - this.minimum; } else { // set new value this.largeChange = value; } this.SetUpScrollBar(); } } /// /// Gets or sets the value. /// [Category("Behavior")] [Description("Gets or sets the current value.")] [DefaultValue(0)] public int Value { get { return this.value; } set { // no change or invalid value - return if (this.value == value || value < this.minimum || value > this.maximum) { return; } this.value = value; // adjust thumb position this.ChangeThumbPosition(this.GetThumbPosition()); // raise scroll event this.OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, -1, this.value, this.scrollOrientation)); this.Refresh(); } } /// /// Gets or sets the list of background markers /// [Category("Behavior")] [Description("Gets or sets the list of background markers.")] [DefaultValue(null)] public List BackgroundMarkers { get { return this.backgroundmarkers; } set { this.backgroundmarkers = value; this.Refresh(); } } /// /// Gets or sets the border color. /// [Category("Appearance")] [Description("Gets or sets the border color.")] [DefaultValue(typeof(Color), "93, 140, 201")] public Color BorderColor { get { return this.borderColor; } set { this.borderColor = value; this.Invalidate(); } } /// /// Gets or sets the border color in disabled state. /// [Category("Appearance")] [Description("Gets or sets the border color in disabled state.")] [DefaultValue(typeof(Color), "Gray")] public Color DisabledBorderColor { get { return this.disabledBorderColor; } set { this.disabledBorderColor = value; this.Invalidate(); } } /// /// Gets or sets the opacity of the context menu (from 0 - 1). /// [Category("Appearance")] [Description("Gets or sets the opacity of the context menu (from 0 - 1).")] [DefaultValue(typeof(double), "1")] public double Opacity { get { return this.contextMenu.Opacity; } set { // no change - return if (value == this.contextMenu.Opacity) { return; } this.contextMenu.AllowTransparency = value != 1; this.contextMenu.Opacity = value; } } /// /// Gets or sets the color of background markers /// [Category("Behavior")] [DefaultValue(typeof(Color), "DarkGray")] [Description("Gets or sets the color of background markers.")] public Color BackgroundMarkerColor { get { return this.backgroundmarkercolor; } set { this.backgroundmarkercolor = value; this.Refresh(); } } #endregion #region methods #region public methods /// /// Prevents the drawing of the control until is called. /// public void BeginUpdate() { SendMessage(this.Handle, SETREDRAW, false, 0); this.inUpdate = true; } /// /// Ends the updating process and the control can draw itself again. /// public void EndUpdate() { SendMessage(this.Handle, SETREDRAW, true, 0); this.inUpdate = false; this.SetUpScrollBar(); this.Refresh(); } #endregion #region protected methods /// /// Raises the event. /// /// The that contains the event data. protected virtual void OnScroll(ScrollEventArgs e) { // if event handler is attached - raise scroll event if (this.Scroll != null) { this.Scroll(this, e); } } /// /// Paints the background of the control. /// /// A that contains information about the control to paint. protected override void OnPaintBackground(PaintEventArgs e) { // no painting here } /// /// Paints the control. /// /// A that contains information about the control to paint. protected override void OnPaint(PaintEventArgs e) { // sets the smoothing mode to none e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None; // save client rectangle Rectangle rect = ClientRectangle; // adjust the rectangle if (this.orientation == ScrollBarOrientation.Vertical) { rect.X++; rect.Y += this.arrowHeight + 1; rect.Width -= 2; rect.Height -= (this.arrowHeight * 2) + 2; } else { rect.X += this.arrowWidth + 1; rect.Y++; rect.Width -= (this.arrowWidth * 2) + 2; rect.Height -= 2; } // draws the background ScrollBarExRenderer.DrawBackground( e.Graphics, ClientRectangle, this.orientation); // draws background markers, if any if ((backgroundmarkers != null) && (backgroundmarkers.Count > 0)) { Pen pen = new Pen(backgroundmarkercolor); foreach (int bm in backgroundmarkers) { int pos = (int)(((double)bm - (double)Minimum) / ((double)Maximum - (double)Minimum) * ClientRectangle.Width); e.Graphics.DrawLine(pen, new Point(pos, ClientRectangle.Top), new Point(pos, ClientRectangle.Bottom)); } } // draws the track ScrollBarExRenderer.DrawTrack( e.Graphics, rect, ScrollBarState.Normal, this.orientation); // draw thumb and grip ScrollBarExRenderer.DrawThumb( e.Graphics, this.thumbRectangle, this.thumbState, this.orientation); if (this.Enabled) { ScrollBarExRenderer.DrawThumbGrip( e.Graphics, this.thumbRectangle, this.orientation); } // draw arrows ScrollBarExRenderer.DrawArrowButton( e.Graphics, this.topArrowRectangle, this.topButtonState, true, this.orientation); ScrollBarExRenderer.DrawArrowButton( e.Graphics, this.bottomArrowRectangle, this.bottomButtonState, false, this.orientation); // check if top or bottom bar was clicked if (this.topBarClicked) { if (this.orientation == ScrollBarOrientation.Vertical) { this.clickedBarRectangle.Y = this.thumbTopLimit; this.clickedBarRectangle.Height = this.thumbRectangle.Y - this.thumbTopLimit; } else { this.clickedBarRectangle.X = this.thumbTopLimit; this.clickedBarRectangle.Width = this.thumbRectangle.X - this.thumbTopLimit; } ScrollBarExRenderer.DrawTrack( e.Graphics, this.clickedBarRectangle, ScrollBarState.Pressed, this.orientation); } else if (this.bottomBarClicked) { if (this.orientation == ScrollBarOrientation.Vertical) { this.clickedBarRectangle.Y = this.thumbRectangle.Bottom + 1; this.clickedBarRectangle.Height = this.thumbBottomLimitBottom - this.clickedBarRectangle.Y + 1; } else { this.clickedBarRectangle.X = this.thumbRectangle.Right + 1; this.clickedBarRectangle.Width = this.thumbBottomLimitBottom - this.clickedBarRectangle.X + 1; } ScrollBarExRenderer.DrawTrack( e.Graphics, this.clickedBarRectangle, ScrollBarState.Pressed, this.orientation); } // draw border using (Pen pen = new Pen( (this.Enabled ? this.borderColor : this.disabledBorderColor))) { e.Graphics.DrawRectangle(pen, 0, 0, this.Width - 1, this.Height - 1); } } /// /// Raises the MouseDown event. /// /// A that contains the event data. protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); this.Focus(); if (e.Button == MouseButtons.Left) { // prevents showing the context menu if pressing the right mouse // button while holding the left this.ContextMenuStrip = null; Point mouseLocation = e.Location; if (this.thumbRectangle.Contains(mouseLocation)) { this.thumbClicked = true; this.thumbPosition = this.orientation == ScrollBarOrientation.Vertical ? mouseLocation.Y - this.thumbRectangle.Y : mouseLocation.X - this.thumbRectangle.X; this.thumbState = ScrollBarState.Pressed; Invalidate(this.thumbRectangle); } else if (this.topArrowRectangle.Contains(mouseLocation)) { this.topArrowClicked = true; this.topButtonState = ScrollBarArrowButtonState.UpPressed; this.Invalidate(this.topArrowRectangle); this.ProgressThumb(true); } else if (this.bottomArrowRectangle.Contains(mouseLocation)) { this.bottomArrowClicked = true; this.bottomButtonState = ScrollBarArrowButtonState.DownPressed; this.Invalidate(this.bottomArrowRectangle); this.ProgressThumb(true); } else { this.trackPosition = this.orientation == ScrollBarOrientation.Vertical ? mouseLocation.Y : mouseLocation.X; if (this.trackPosition < (this.orientation == ScrollBarOrientation.Vertical ? this.thumbRectangle.Y : this.thumbRectangle.X)) { this.topBarClicked = true; } else { this.bottomBarClicked = true; } this.ProgressThumb(true); } } else if (e.Button == MouseButtons.Right) { this.trackPosition = this.orientation == ScrollBarOrientation.Vertical ? e.Y : e.X; } } /// /// Raises the MouseUp event. /// /// A that contains the event data. protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (e.Button == MouseButtons.Left) { this.ContextMenuStrip = this.contextMenu; if (this.thumbClicked) { this.thumbClicked = false; this.thumbState = ScrollBarState.Normal; this.OnScroll(new ScrollEventArgs( ScrollEventType.EndScroll, -1, this.value, this.scrollOrientation) ); } else if (this.topArrowClicked) { this.topArrowClicked = false; this.topButtonState = ScrollBarArrowButtonState.UpNormal; this.StopTimer(); } else if (this.bottomArrowClicked) { this.bottomArrowClicked = false; this.bottomButtonState = ScrollBarArrowButtonState.DownNormal; this.StopTimer(); } else if (this.topBarClicked) { this.topBarClicked = false; this.StopTimer(); } else if (this.bottomBarClicked) { this.bottomBarClicked = false; this.StopTimer(); } Invalidate(); } } /// /// Raises the MouseEnter event. /// /// A that contains the event data. protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); this.bottomButtonState = ScrollBarArrowButtonState.DownActive; this.topButtonState = ScrollBarArrowButtonState.UpActive; this.thumbState = ScrollBarState.Active; Invalidate(); } /// /// Raises the MouseLeave event. /// /// A that contains the event data. protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); this.ResetScrollStatus(); } /// /// Raises the MouseMove event. /// /// A that contains the event data. protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); // moving and holding the left mouse button if (e.Button == MouseButtons.Left) { // Update the thumb position, if the new location is within the bounds. if (this.thumbClicked) { int oldScrollValue = this.value; this.topButtonState = ScrollBarArrowButtonState.UpActive; this.bottomButtonState = ScrollBarArrowButtonState.DownActive; int pos = this.orientation == ScrollBarOrientation.Vertical ? e.Location.Y : e.Location.X; // The thumb is all the way to the top if (pos <= (this.thumbTopLimit + this.thumbPosition)) { this.ChangeThumbPosition(this.thumbTopLimit); this.value = this.minimum; } else if (pos >= (this.thumbBottomLimitTop + this.thumbPosition)) { // The thumb is all the way to the bottom this.ChangeThumbPosition(this.thumbBottomLimitTop); this.value = this.maximum; } else { // The thumb is between the ends of the track. this.ChangeThumbPosition(pos - this.thumbPosition); int pixelRange, thumbPos, arrowSize; // calculate the value - first some helper variables // dependent on the current orientation if (this.orientation == ScrollBarOrientation.Vertical) { pixelRange = this.Height - (2 * this.arrowHeight) - this.thumbHeight; thumbPos = this.thumbRectangle.Y; arrowSize = this.arrowHeight; } else { pixelRange = this.Width - (2 * this.arrowWidth) - this.thumbWidth; thumbPos = this.thumbRectangle.X; arrowSize = this.arrowWidth; } float perc = 0f; if (pixelRange != 0) { // percent of the new position perc = (float)(thumbPos - arrowSize) / (float)pixelRange; } // the new value is somewhere between max and min, starting // at min position this.value = Convert.ToInt32((perc * (this.maximum - this.minimum)) + this.minimum); } // raise scroll event if new value different if (oldScrollValue != this.value) { this.OnScroll(new ScrollEventArgs(ScrollEventType.ThumbTrack, oldScrollValue, this.value, this.scrollOrientation)); this.Refresh(); } } } else if (!this.ClientRectangle.Contains(e.Location)) { this.ResetScrollStatus(); } else if (e.Button == MouseButtons.None) // only moving the mouse { if (this.topArrowRectangle.Contains(e.Location)) { this.topButtonState = ScrollBarArrowButtonState.UpHot; this.Invalidate(this.topArrowRectangle); } else if (this.bottomArrowRectangle.Contains(e.Location)) { this.bottomButtonState = ScrollBarArrowButtonState.DownHot; Invalidate(this.bottomArrowRectangle); } else if (this.thumbRectangle.Contains(e.Location)) { this.thumbState = ScrollBarState.Hot; this.Invalidate(this.thumbRectangle); } else if (this.ClientRectangle.Contains(e.Location)) { this.topButtonState = ScrollBarArrowButtonState.UpActive; this.bottomButtonState = ScrollBarArrowButtonState.DownActive; this.thumbState = ScrollBarState.Active; Invalidate(); } } } /// /// Performs the work of setting the specified bounds of this control. /// /// The new x value of the control. /// The new y value of the control. /// The new width value of the control. /// The new height value of the control. /// A bitwise combination of the values. protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) { // only in design mode - constrain size if (this.DesignMode) { if (this.orientation == ScrollBarOrientation.Vertical) { if (height < (2 * this.arrowHeight) + 10) { height = (2 * this.arrowHeight) + 10; } width = 19; } else { if (width < (2 * this.arrowWidth) + 10) { width = (2 * this.arrowWidth) + 10; } height = 19; } } base.SetBoundsCore(x, y, width, height, specified); if (this.DesignMode) { this.SetUpScrollBar(); } } /// /// Raises the event. /// /// An that contains the event data. protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); this.SetUpScrollBar(); } /// /// Processes a dialog key. /// /// One of the values that represents the key to process. /// true, if the key was processed by the control, false otherwise. protected override bool ProcessDialogKey(Keys keyData) { // key handling is here - keys recognized by the control // Up&Down or Left&Right, PageUp, PageDown, Home, End Keys keyUp = Keys.Up; Keys keyDown = Keys.Down; if (this.orientation == ScrollBarOrientation.Horizontal) { keyUp = Keys.Left; keyDown = Keys.Right; } if (keyData == keyUp) { this.Value -= this.smallChange; return true; } if (keyData == keyDown) { this.Value += this.smallChange; return true; } if (keyData == Keys.PageUp) { this.Value = this.GetValue(false, true); return true; } if (keyData == Keys.PageDown) { if (this.value + this.largeChange > this.maximum) { this.Value = this.maximum; } else { this.Value += this.largeChange; } return true; } if (keyData == Keys.Home) { this.Value = this.minimum; return true; } if (keyData == Keys.End) { this.Value = this.maximum; return true; } return base.ProcessDialogKey(keyData); } /// /// Raises the event. /// /// An that contains the event data. protected override void OnEnabledChanged(EventArgs e) { base.OnEnabledChanged(e); if (this.Enabled) { this.thumbState = ScrollBarState.Normal; this.topButtonState = ScrollBarArrowButtonState.UpNormal; this.bottomButtonState = ScrollBarArrowButtonState.DownNormal; } else { this.thumbState = ScrollBarState.Disabled; this.topButtonState = ScrollBarArrowButtonState.UpDisabled; this.bottomButtonState = ScrollBarArrowButtonState.DownDisabled; } this.Refresh(); } #endregion #region misc methods /// /// Sends a message. /// /// The handle of the control. /// The message as int. /// param - true or false. /// Additional parameter. /// 0 or error code. /// Needed for sending the stop/start drawing of the control. [DllImport("user32.dll")] private static extern int SendMessage( IntPtr wnd, int msg, bool param, int lparam); /// /// Sets up the scrollbar. /// private void SetUpScrollBar() { // if no drawing - return if (this.inUpdate) { return; } // set up the width's, height's and rectangles for the different // elements if (this.orientation == ScrollBarOrientation.Vertical) { this.arrowHeight = 17; this.arrowWidth = 15; this.thumbWidth = 15; this.thumbHeight = this.GetThumbSize(); this.clickedBarRectangle = this.ClientRectangle; this.clickedBarRectangle.Inflate(-1, -1); this.clickedBarRectangle.Y += this.arrowHeight; this.clickedBarRectangle.Height -= this.arrowHeight * 2; this.channelRectangle = this.clickedBarRectangle; this.thumbRectangle = new Rectangle( ClientRectangle.X + 2, ClientRectangle.Y + this.arrowHeight + 1, this.thumbWidth - 1, this.thumbHeight ); this.topArrowRectangle = new Rectangle( ClientRectangle.X + 2, ClientRectangle.Y + 1, this.arrowWidth, this.arrowHeight ); this.bottomArrowRectangle = new Rectangle( ClientRectangle.X + 2, ClientRectangle.Bottom - this.arrowHeight - 1, this.arrowWidth, this.arrowHeight ); // Set the default starting thumb position. this.thumbPosition = this.thumbRectangle.Height / 2; // Set the bottom limit of the thumb's bottom border. this.thumbBottomLimitBottom = ClientRectangle.Bottom - this.arrowHeight - 2; // Set the bottom limit of the thumb's top border. this.thumbBottomLimitTop = this.thumbBottomLimitBottom - this.thumbRectangle.Height; // Set the top limit of the thumb's top border. this.thumbTopLimit = ClientRectangle.Y + this.arrowHeight + 1; } else { this.arrowHeight = 15; this.arrowWidth = 17; this.thumbHeight = 15; this.thumbWidth = this.GetThumbSize(); this.clickedBarRectangle = this.ClientRectangle; this.clickedBarRectangle.Inflate(-1, -1); this.clickedBarRectangle.X += this.arrowWidth; this.clickedBarRectangle.Width -= this.arrowWidth * 2; this.channelRectangle = this.clickedBarRectangle; this.thumbRectangle = new Rectangle( ClientRectangle.X + this.arrowWidth + 1, ClientRectangle.Y + 2, this.thumbWidth, this.thumbHeight - 1 ); this.topArrowRectangle = new Rectangle( ClientRectangle.X + 1, ClientRectangle.Y + 2, this.arrowWidth, this.arrowHeight ); this.bottomArrowRectangle = new Rectangle( ClientRectangle.Right - this.arrowWidth - 1, ClientRectangle.Y + 2, this.arrowWidth, this.arrowHeight ); // Set the default starting thumb position. this.thumbPosition = this.thumbRectangle.Width / 2; // Set the bottom limit of the thumb's bottom border. this.thumbBottomLimitBottom = ClientRectangle.Right - this.arrowWidth - 2; // Set the bottom limit of the thumb's top border. this.thumbBottomLimitTop = this.thumbBottomLimitBottom - this.thumbRectangle.Width; // Set the top limit of the thumb's top border. this.thumbTopLimit = ClientRectangle.X + this.arrowWidth + 1; } this.ChangeThumbPosition(this.GetThumbPosition()); this.Refresh(); } /// /// Handles the updating of the thumb. /// /// The sender. /// An object that contains the event data. private void ProgressTimerTick(object sender, EventArgs e) { this.ProgressThumb(true); } /// /// Resets the scroll status of the scrollbar. /// private void ResetScrollStatus() { // get current mouse position Point pos = this.PointToClient(Cursor.Position); // set appearance of buttons in relation to where the mouse is - // outside or inside the control if (this.ClientRectangle.Contains(pos)) { this.bottomButtonState = ScrollBarArrowButtonState.DownActive; this.topButtonState = ScrollBarArrowButtonState.UpActive; } else { this.bottomButtonState = ScrollBarArrowButtonState.DownNormal; this.topButtonState = ScrollBarArrowButtonState.UpNormal; } // set appearance of thumb this.thumbState = this.thumbRectangle.Contains(pos) ? ScrollBarState.Hot : ScrollBarState.Normal; this.bottomArrowClicked = this.bottomBarClicked = this.topArrowClicked = this.topBarClicked = false; this.StopTimer(); this.Refresh(); } /// /// Calculates the new value of the scrollbar. /// /// true for a small change, false otherwise. /// true for up movement, false otherwise. /// The new scrollbar value. private int GetValue(bool smallIncrement, bool up) { int newValue; // calculate the new value of the scrollbar // with checking if new value is in bounds (min/max) if (up) { newValue = this.value - (smallIncrement ? this.smallChange : this.largeChange); if (newValue < this.minimum) { newValue = this.minimum; } } else { newValue = this.value + (smallIncrement ? this.smallChange : this.largeChange); if (newValue > this.maximum) { newValue = this.maximum; } } return newValue; } /// /// Calculates the new thumb position. /// /// The new thumb position. private int GetThumbPosition() { int pixelRange, arrowSize; if (this.orientation == ScrollBarOrientation.Vertical) { pixelRange = this.Height - (2 * this.arrowHeight) - this.thumbHeight; arrowSize = this.arrowHeight; } else { pixelRange = this.Width - (2 * this.arrowWidth) - this.thumbWidth; arrowSize = this.arrowWidth; } int realRange = this.maximum - this.minimum; float perc = 0f; if (realRange != 0) { perc = ((float)this.value - (float)this.minimum) / (float)realRange; } return Math.Max(this.thumbTopLimit, Math.Min( this.thumbBottomLimitTop, Convert.ToInt32((perc * pixelRange) + arrowSize))); } /// /// Calculates the height of the thumb. /// /// The height of the thumb. private int GetThumbSize() { int trackSize = this.orientation == ScrollBarOrientation.Vertical ? this.Height - (2 * this.arrowHeight) : this.Width - (2 * this.arrowWidth); if (this.maximum == 0 || this.largeChange == 0) { return trackSize; } float newThumbSize = ((float)this.largeChange * (float)trackSize) / (float)this.maximum; return Convert.ToInt32(Math.Min((float)trackSize, Math.Max(newThumbSize, 10f))); } /// /// Enables the timer. /// private void EnableTimer() { // if timer is not already enabled - enable it if (!this.progressTimer.Enabled) { this.progressTimer.Interval = 600; this.progressTimer.Start(); } else { // if already enabled, change tick time this.progressTimer.Interval = 10; } } /// /// Stops the progress timer. /// private void StopTimer() { this.progressTimer.Stop(); } /// /// Changes the position of the thumb. /// /// The new position. private void ChangeThumbPosition(int position) { if (this.orientation == ScrollBarOrientation.Vertical) { this.thumbRectangle.Y = position; } else { this.thumbRectangle.X = position; } } /// /// Controls the movement of the thumb. /// /// true for enabling the timer, false otherwise. private void ProgressThumb(bool enableTimer) { int scrollOldValue = this.value; ScrollEventType type = ScrollEventType.First; int thumbSize, thumbPos; if (this.orientation == ScrollBarOrientation.Vertical) { thumbPos = this.thumbRectangle.Y; thumbSize = this.thumbRectangle.Height; } else { thumbPos = this.thumbRectangle.X; thumbSize = this.thumbRectangle.Width; } // arrow down or shaft down clicked if (this.bottomArrowClicked || (this.bottomBarClicked && (thumbPos + thumbSize) < this.trackPosition)) { type = this.bottomArrowClicked ? ScrollEventType.SmallIncrement : ScrollEventType.LargeIncrement; this.value = this.GetValue(this.bottomArrowClicked, false); if (this.value == this.maximum) { this.ChangeThumbPosition(this.thumbBottomLimitTop); type = ScrollEventType.Last; } else { this.ChangeThumbPosition(Math.Min(this.thumbBottomLimitTop, this.GetThumbPosition())); } } else if (this.topArrowClicked || (this.topBarClicked && thumbPos > this.trackPosition)) { type = this.topArrowClicked ? ScrollEventType.SmallDecrement : ScrollEventType.LargeDecrement; // arrow up or shaft up clicked this.value = this.GetValue(this.topArrowClicked, true); if (this.value == this.minimum) { this.ChangeThumbPosition(this.thumbTopLimit); type = ScrollEventType.First; } else { this.ChangeThumbPosition(Math.Max(this.thumbTopLimit, this.GetThumbPosition())); } } else if (!((this.topArrowClicked && thumbPos == this.thumbTopLimit) || (this.bottomArrowClicked && thumbPos == this.thumbBottomLimitTop))) { this.ResetScrollStatus(); return; } if (scrollOldValue != this.value) { this.OnScroll(new ScrollEventArgs(type, scrollOldValue, this.value, this.scrollOrientation)); this.Invalidate(this.channelRectangle); if (enableTimer) { this.EnableTimer(); } } else { if (this.topArrowClicked) { type = ScrollEventType.SmallDecrement; } else if (this.bottomArrowClicked) { type = ScrollEventType.SmallIncrement; } this.OnScroll(new ScrollEventArgs(type, this.value)); } } /// /// Changes the displayed text of the context menu items dependent of the current . /// private void ChangeContextMenuItems() { if (this.orientation == ScrollBarOrientation.Vertical) { this.tsmiTop.Text = "Top"; this.tsmiBottom.Text = "Bottom"; this.tsmiLargeDown.Text = "Page down"; this.tsmiLargeUp.Text = "Page up"; this.tsmiSmallDown.Text = "Scroll down"; this.tsmiSmallUp.Text = "Scroll up"; this.tsmiScrollHere.Text = "Scroll here"; } else { this.tsmiTop.Text = "Left"; this.tsmiBottom.Text = "Right"; this.tsmiLargeDown.Text = "Page left"; this.tsmiLargeUp.Text = "Page right"; this.tsmiSmallDown.Text = "Scroll right"; this.tsmiSmallUp.Text = "Scroll left"; this.tsmiScrollHere.Text = "Scroll here"; } } #endregion #region context menu methods /// /// Initializes the context menu. /// private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.contextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.tsmiScrollHere = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.tsmiTop = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiBottom = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.tsmiLargeUp = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiLargeDown = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); this.tsmiSmallUp = new System.Windows.Forms.ToolStripMenuItem(); this.tsmiSmallDown = new System.Windows.Forms.ToolStripMenuItem(); this.contextMenu.SuspendLayout(); this.SuspendLayout(); // // contextMenu // this.contextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsmiScrollHere, this.toolStripSeparator1, this.tsmiTop, this.tsmiBottom, this.toolStripSeparator2, this.tsmiLargeUp, this.tsmiLargeDown, this.toolStripSeparator3, this.tsmiSmallUp, this.tsmiSmallDown}); this.contextMenu.Name = "contextMenu"; this.contextMenu.Size = new System.Drawing.Size(151, 176); // // tsmiScrollHere // this.tsmiScrollHere.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiScrollHere.Name = "tsmiScrollHere"; this.tsmiScrollHere.Size = new System.Drawing.Size(150, 22); this.tsmiScrollHere.Text = "Scroll here"; this.tsmiScrollHere.Click += ScrollHereClick; // // toolStripSeparator1 // this.toolStripSeparator1.Name = "toolStripSeparator1"; this.toolStripSeparator1.Size = new System.Drawing.Size(147, 6); // // tsmiTop // this.tsmiTop.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiTop.Name = "tsmiTop"; this.tsmiTop.Size = new System.Drawing.Size(150, 22); this.tsmiTop.Text = "Top"; this.tsmiTop.Click += this.TopClick; // // tsmiBottom // this.tsmiBottom.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiBottom.Name = "tsmiBottom"; this.tsmiBottom.Size = new System.Drawing.Size(150, 22); this.tsmiBottom.Text = "Bottom"; this.tsmiBottom.Click += this.BottomClick; // // toolStripSeparator2 // this.toolStripSeparator2.Name = "toolStripSeparator2"; this.toolStripSeparator2.Size = new System.Drawing.Size(147, 6); // // tsmiLargeUp // this.tsmiLargeUp.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiLargeUp.Name = "tsmiLargeUp"; this.tsmiLargeUp.Size = new System.Drawing.Size(150, 22); this.tsmiLargeUp.Text = "Page up"; this.tsmiLargeUp.Click += this.LargeUpClick; // // tsmiLargeDown // this.tsmiLargeDown.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiLargeDown.Name = "tsmiLargeDown"; this.tsmiLargeDown.Size = new System.Drawing.Size(150, 22); this.tsmiLargeDown.Text = "Page down"; this.tsmiLargeDown.Click += this.LargeDownClick; // // toolStripSeparator3 // this.toolStripSeparator3.Name = "toolStripSeparator3"; this.toolStripSeparator3.Size = new System.Drawing.Size(147, 6); // // tsmiSmallUp // this.tsmiSmallUp.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiSmallUp.Name = "tsmiSmallUp"; this.tsmiSmallUp.Size = new System.Drawing.Size(150, 22); this.tsmiSmallUp.Text = "Scroll up"; this.tsmiSmallUp.Click += this.SmallUpClick; // // tsmiSmallDown // this.tsmiSmallDown.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.tsmiSmallDown.Name = "tsmiSmallDown"; this.tsmiSmallDown.Size = new System.Drawing.Size(150, 22); this.tsmiSmallDown.Text = "Scroll down"; this.tsmiSmallDown.Click += this.SmallDownClick; this.contextMenu.ResumeLayout(false); this.ResumeLayout(false); } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void ScrollHereClick(object sender, EventArgs e) { int thumbSize, thumbPos, arrowSize, size; if (this.orientation == ScrollBarOrientation.Vertical) { thumbSize = this.thumbHeight; arrowSize = this.arrowHeight; size = this.Height; this.ChangeThumbPosition(Math.Max(this.thumbTopLimit, Math.Min(this.thumbBottomLimitTop, this.trackPosition - (this.thumbRectangle.Height / 2)))); thumbPos = this.thumbRectangle.Y; } else { thumbSize = this.thumbWidth; arrowSize = this.arrowWidth; size = this.Width; this.ChangeThumbPosition(Math.Max(this.thumbTopLimit, Math.Min(this.thumbBottomLimitTop, this.trackPosition - (this.thumbRectangle.Width / 2)))); thumbPos = this.thumbRectangle.X; } int pixelRange = size - (2 * arrowSize) - thumbSize; float perc = 0f; if (pixelRange != 0) { perc = (float)(thumbPos - arrowSize) / (float)pixelRange; } int oldValue = this.value; this.value = Convert.ToInt32((perc * (this.maximum - this.minimum)) + this.minimum); this.OnScroll(new ScrollEventArgs(ScrollEventType.ThumbPosition, oldValue, this.value, this.scrollOrientation)); this.Refresh(); } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void TopClick(object sender, EventArgs e) { this.Value = this.minimum; } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void BottomClick(object sender, EventArgs e) { this.Value = this.maximum; } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void LargeUpClick(object sender, EventArgs e) { this.Value = this.GetValue(false, true); } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void LargeDownClick(object sender, EventArgs e) { this.Value = this.GetValue(false, false); } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void SmallUpClick(object sender, EventArgs e) { this.Value = this.GetValue(true, true); } /// /// Context menu handler. /// /// The sender. /// The event arguments. private void SmallDownClick(object sender, EventArgs e) { this.Value = this.GetValue(true, false); } #endregion #endregion } }