ExternalScrollbarListBox : a listbox with an external scrollbar

Download externalscrollbarlistbox.zip

Synopsis:

Form1.cs
control.cs
LiveScrollBar.cs
MasterScrollBar.cs
NullScrollBar.cs
ScrollBarWrapper.cs
SlaveScrollBar.cs


Form1.cs

Synopsis
using System;
using System.Windows.Forms;
using nsExternalScrollbarListBox;

namespace ExternalScrollbarListBox_test
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
    private ExternalScrollbarListBox[] mLists = new ExternalScrollbarListBox[5];
    private System.Windows.Forms.VScrollBar vScrollBar1;

    /// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();
      bool master = true;
      for(int n = 0; n < mLists.Length; ++n)
      {
        mLists[n] = new ExternalScrollbarListBox();
        for(int i = 0; i < 30; ++i)
        {
          mLists[n].Add(String.Format("{0,3} list{1}", i, n+1));
        }
        mLists[n].Name = "mList" + n;
        mLists[n].TabIndex = n;

        mLists[n].SetScrollBar(vScrollBar1, master);
        master = false;
      }
      this.Controls.AddRange(mLists);
    }

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
      this.vScrollBar1 = new System.Windows.Forms.VScrollBar();
      this.SuspendLayout();
      // 
      // vScrollBar1
      // 
      this.vScrollBar1.Location = new System.Drawing.Point(376, 184);
      this.vScrollBar1.Name = "vScrollBar1";
      this.vScrollBar1.Size = new System.Drawing.Size(16, 192);
      this.vScrollBar1.TabIndex = 1;
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(648, 525);
      this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                                  this.vScrollBar1});
      this.Name = "Form1";
      this.Text = "Form1";
      this.Layout += new System.Windows.Forms.LayoutEventHandler(this.Form1_HandleLayout);
      this.ResumeLayout(false);

    }
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}
    private void Form1_HandleLayout(object sender, LayoutEventArgs e)
    {
      int x = 10; 
      int y = 10;
      int width = 100;
      int height = this.ClientSize.Height - 20;
      for(int n = 0; n < mLists.Length; ++n)
      {
        mLists[n].SetBounds(x, y, width, height);
        x += width;
      }
      vScrollBar1.SetBounds(x, y, vScrollBar1.Width, height);
    }
	}
}

control.cs

Synopsis
using System;
using System.Collections;
using System.Windows.Forms;
using System.Drawing;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// A ListBox-like control that has an external scrollbar
  /// </summary>
  public class ExternalScrollbarListBox : ListControl
  {
    private int cBorderWidth = SystemInformation.Border3DSize.Width;

    private ScrollBarWrapper mScrollBar;
    private int mSelectedIndex = -1;
    private int mTopRow = 0;
    private DrawMode mDrawMode = DrawMode.Normal;
    private ArrayList mDrawItemDelegates = new ArrayList();
    private ArrayList mList = new ArrayList();
    private System.ComponentModel.Container components = null;

    public ExternalScrollbarListBox()
    {
      Init();
      mScrollBar = new NullScrollBar();
    }

    public event EventHandler SelectedIndexChanged;
    public event DrawItemEventHandler DrawItem;
    public delegate void DrawItemEventHandler(object sender, DrawItemEventArgs e);

    public DrawMode DrawMode
    {
      set { mDrawMode = value; }
    }
    public object SelectedItem
    {
      set { mList[SelectedIndex] = value; }
      get { return mList[SelectedIndex]; }
    }
    public void SetScrollBar(VScrollBar sb, bool master)
    {
      if (master)
        mScrollBar = new MasterScrollBar(this, sb); 
      else
        mScrollBar = new SlaveScrollBar(this, sb); 
    }
    public void Add(object s)
    {
      mList.Add(s);
      Invalidate();
    }
    public void AddRange(object[] r)
    {
      mList.AddRange(r);
      Invalidate();
    }
    public void Clear()
    {
      mList.Clear();
      Invalidate();
    }
    public ArrayList Items
    {
      get { return mList; }
    }
    public int IndexFromPoint(Point p)
    {
      int row = mTopRow + (p.Y - cBorderWidth) / RowHeight;
      if (row >= mList.Count) return -1;
      return row;
    }
    public int TopRow
    {
      set 
      { 
        if (mTopRow == value) return;
        mTopRow = value; 
        Invalidate(); 
      }
    }
    public override int SelectedIndex
    {
      set { mSelectedIndex = value; }
      get { return mSelectedIndex; }
    }
    public new void SetBounds(int x, int y, int width, int height, BoundsSpecified specified)
    {
      base.SetBounds(x, y, width, height, specified);
      Invalidate();
    }
    public new void SetBounds(int x, int y, int width, int height)
    {
      base.SetBounds(x, y, width, height);
      Invalidate();
    }
    //--------- protected from here on
    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if( components != null )
          components.Dispose();
      }
      base.Dispose( disposing );
    }
    protected void HandleMouseDown(object sender, MouseEventArgs e)
    {
      int row = IndexFromPoint(new Point(e.X, e.Y));
      if (row < 0) return;

      Rectangle rct = new Rectangle(cBorderWidth, ((mSelectedIndex - mTopRow) * RowHeight) + cBorderWidth, this.Width - cBorderWidth, RowHeight);
      if (mSelectedIndex >= mTopRow)
        Invalidate(rct);
      mSelectedIndex = row;
      rct.Y = ((mSelectedIndex - mTopRow) * RowHeight) + cBorderWidth;
      Invalidate(rct);
      this.OnSelectedIndexChanged(EventArgs.Empty);
    }
    protected override void SetItemsCore(System.Collections.IList l)
    {
    }
    protected override void RefreshItem(int i)
    {
    }

    //--------- private from here on
    private new void OnSelectedIndexChanged(EventArgs e)
    {
      if (SelectedIndexChanged == null) return;
      SelectedIndexChanged(this, e);
    }

    //Removed the designer so that it didn't change this section
    private void Init()
    {
      this.SuspendLayout();

      this.BackColor = System.Drawing.Color.White;
      this.CausesValidation = false;
      this.Location = new System.Drawing.Point(8, 8);
      this.Name = "mTextList";
      this.SelectedIndex = 0;
      this.Size = new System.Drawing.Size(88, 80);
      this.TabIndex = 0;
      this.Text = "mTextList";
      this.TopRow = 0;
      this.Name = "ExternalScrollbarListBox";
      this.Size = new System.Drawing.Size(104, 96);
      this.BackColor = Color.White;
      this.Paint += new PaintEventHandler(HandlePaint);
      this.MouseDown += new MouseEventHandler(HandleMouseDown);

      this.ResumeLayout(false);
    }
    private int RowHeight
    {
      get { return this.Font.Height; }
    }

    //paint routines from here on
    private void HandlePaint(object sender, PaintEventArgs e)
    {
      PaintBackground(e.Graphics);
      PaintBorder(e.Graphics);
      PaintList(e.Graphics);
      mScrollBar.PaintScrollBar(this.mList.Count, VisibleRows);
    }
    private void PaintBackground(Graphics g)
    {
      g.FillRectangle(new SolidBrush(this.BackColor), 0, 0, this.Width, this.Height);
    }
    private void PaintBorder(Graphics g)
    {
      ControlPaint.DrawBorder3D(g, 0, 0, this.Width, this.Height);
    }
    private void PaintList(Graphics g)
    {
      MakeBottomRowVisible();
      Rectangle rct = GetRowRectangle();
      for(int row = this.mTopRow; row < this.mList.Count && CanPaintRow(rct); ++row, NextRow(ref rct))
        PaintRow(g, row, rct);
    }
    private void MakeBottomRowVisible()
    {
      if (this.mTopRow < HiddenRows) return;
      this.mTopRow = HiddenRows;
      this.mScrollBar.Value = mTopRow;
    }
    private Rectangle GetRowRectangle()
    {
      return new Rectangle(cBorderWidth, cBorderWidth, this.Width - cBorderWidth, this.RowHeight);
    }
    private void PaintRow(Graphics g, int row, Rectangle rct)
    {
      if (row == this.mSelectedIndex)
        PaintSelectedRow(g, row, rct);
      else
        PaintRow(g, row, rct, new SolidBrush(this.ForeColor), DrawItemState.Default);
    }
    private void PaintSelectedRow(Graphics g, int row, Rectangle rct)
    {
      g.FillRectangle(new SolidBrush(Color.Blue), rct);
      PaintRow(g, row, rct, new SolidBrush(Color.White), DrawItemState.Selected);
    }
    private void PaintRow(Graphics g, int row, Rectangle rct, SolidBrush forecolor, DrawItemState state)
    {
      if (this.mDrawMode == System.Windows.Forms.DrawMode.Normal)
        PaintRow(g, row, rct, forecolor);
      else if (this.mDrawMode == System.Windows.Forms.DrawMode.OwnerDrawFixed)
        PaintOwnerDrawRow(g, rct, row, state);
    }
    private void PaintOwnerDrawRow(Graphics g, Rectangle rct, int row, DrawItemState state)
    {
      this.OnDrawItem(new DrawItemEventArgs(g, this.Font, rct, row, state, this.ForeColor, this.BackColor));
    }
    private void OnDrawItem(DrawItemEventArgs e)
    {
      if (DrawItem == null) return;
      DrawItem(this, e);
    }
    private void PaintRow(Graphics g, int row, Rectangle rct, SolidBrush forecolor)
    {
      g.DrawString(mList[row].ToString(), this.Font, forecolor, rct, StringFormat.GenericDefault);
    }
    private void NextRow(ref Rectangle rct)
    {
      rct.Y += RowHeight;
    }
    private bool CanPaintRow(RectangleF rct)
    {
      return (rct.Y + RowHeight) <= (this.Height - cBorderWidth);
    }
    private int HiddenRows
    {
      get { return Math.Max(mList.Count - VisibleRows, 0); }
    }
    private int VisibleRows
    {
      get { return VisibleHeight / RowHeight; }
    }
    private int VisibleHeight
    {
      get { return this.Height - (2 * cBorderWidth); }
    }
  }
}

LiveScrollBar.cs

Synopsis
using System;
using System.Windows.Forms;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// Base class for "live" scroll bars, i.e. those that respond to the Scroll Event
  /// </summary>
  abstract class LiveScrollBar : ScrollBarWrapper
  {
    protected ExternalScrollbarListBox mParent;
    protected ScrollBar mScrollBar = null;

    public LiveScrollBar(ExternalScrollbarListBox parent, ScrollBar sb)
    {
      mParent = parent;
      mScrollBar = sb;
      mScrollBar.Scroll += new ScrollEventHandler(HandleScroll);
    }
    protected void HandleScroll(object sender, ScrollEventArgs e)
    {
      if (e.Type == ScrollEventType.EndScroll) return;
      mParent.TopRow  = e.NewValue;
    }
  }
}

MasterScrollBar.cs

Synopsis
using System;
using System.Windows.Forms;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// Controls the external scrollbar
  /// There should be one and only one master in the group of sync'ed TextLists
  /// </summary>
  class MasterScrollBar : LiveScrollBar
  {
    public MasterScrollBar(ExternalScrollbarListBox parent, ScrollBar sb)
      : base(parent, sb)
    {
      mScrollBar.Visible = false;
    }
    public override int Value
    {
      set { mScrollBar.Value = value; }
    }
    public override void PaintScrollBar(int count, int visiblerows)
    {
      mScrollBar.Minimum = 0;
      mScrollBar.Maximum = Math.Max(count - 1, 0);
      mScrollBar.LargeChange = Math.Max(visiblerows, 1);
      mScrollBar.SmallChange = Math.Max(mScrollBar.LargeChange / 5, 1);
      mScrollBar.Visible = count > 1 && count > visiblerows;
    }
  }
}

NullScrollBar.cs

Synopsis
using System;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// no-op class (Null Object Pattern)
  /// </summary>
  class NullScrollBar : ScrollBarWrapper
  {
  }
}

ScrollBarWrapper.cs

Synopsis
using System;
using System.Windows.Forms;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// base class for all the scrollbar classes
  /// </summary>
  abstract class ScrollBarWrapper
  {
    public virtual int Value { set  { }  }
    public virtual void PaintScrollBar(int count, int visiblerows)  { }
  }
}

SlaveScrollBar.cs

Synopsis
using System;
using System.Windows.Forms;

namespace nsExternalScrollbarListBox
{
  /// <summary>
  /// A slave scrollbar handles just the incoming Scroll Event
  /// </summary>
  class SlaveScrollBar : LiveScrollBar
  {
    public SlaveScrollBar(ExternalScrollbarListBox parent, ScrollBar sb)
      : base(parent, sb)
    {
    }
  }
}






Contact me about content on this page using john_web-at-arrizza-dot-com
For Web Master or site problems contact: webadmin-at-arrizza-dot-com
Copyright John Arrizza (c) 2001-2010