
Introduction
The screenshot above shows you what it's all about: I wrote scroll bars from scratch in C#. I tried to develop scroll bars which would look exactly like the Motif scroll bars (in case you are wondering what Motif is, it is a rather old toolkit to develop GUI programs under X Windows on Unix's).
The scroll bars have the same name as the .NET scroll bars, but they are in the Motif
namespace. If you want to use the scroll bars in your own programs, you have to include these three files:
- ControlPaint.cs
- MotifColors.cs
- ScrollBars.cs
ControlPaint.cs contains the code to draw arrows and borders. MotifColors.cs contains the colors of the controls. You can change these colors to the standard Windows colors if you want: take a look at the SystemColors
class in the .NET documentation. ScrollBars.cs contains three classes: ScrollBar
, HScrollBar
, and VScrollBar
.
The scroll bars are not selectable, so they cannot have focus and the thumb will not blink (is there anyone who actually likes a blinking thumb?).
There is one very important difference between the Motif scroll bars and the .NET scroll bars: the .NET scroll bars cannot reach the maximum value through user interaction at runtime, the Motif scroll bars can. The maximum value the .NET scroll bars can reach is equal to the Maximum
property value minus the LargeChange
property value plus one (Maximum - LargeChange + 1
). So, if you set LargeChange
to 10 and Maximum
to 100, the maximum value a .NET scroll bar can reach (through user interaction) will be 100 - 10 + 1 = 91. A Motif scroll bar will reach 100.
The arrows are painted by creating a bitmap and then drawing them pixel by pixel. I did this so that they would look exactly like the Motif scroll bars (Motif paints an arrow by drawing three triangles in different colors at different offsets). I also make a distinction between arrows with even and uneven heights, again to control each and every pixel. But let's face it, this is not really efficient. The up and down arrows are painted by flipping and mirroring a left or right arrow. The DrawImage()
method of the Graphics
class takes care of scaling.
The source code of the scroll bars is also interesting for the logic of a scroll bar. If you want to create a scroll bar from scratch in OpenGL, you can take a look at the source code and start from there instead of figuring out how to do it (but don't we programmers always want to start from scratch?).
The ScrolledWindow Control
Now that we have a horizontal and a vertical scroll bar, what can we do with it? We could, for example, put three scroll bars on a form to create a color chooser: one scroll bar to choose the red component, one to choose the green component, and a third to choose the blue component. We would have to set the Minimum
property of the scroll bars to 0, and the Maximum
property to 255. Nice, but can we do better?
The ScrolledWindow
control is a control that demonstrates how to use the Motif scroll bars to scroll another control. In the image above, you can see the ScrolledWindow
in action. The ScrolledWindow
control consists of a horizontal scroll bar, a vertical scroll bar, and a Panel
. The ScrolledWindow
control has a property Control
which is the first child control of the panel. It is this control that can be scrolled using the Motif scroll bars.
Here is the code of the ScrolledWindow
control:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Motif
{
public partial class ScrolledWindow : UserControl
{
public ScrolledWindow()
{
InitializeComponent();
SetStyle(
ControlStyles.ResizeRedraw, true
);
}
public Control Control
{
get
{
if (this.panel.Controls.Count > 0)
return this.panel.Controls[0];
else
return null;
}
set
{
this.panel.Controls.Add(value);
UpdateScrollBars();
}
}
void UpdateScrollBars()
{
Control control = this.Control;
this.hScrollBar.Maximum = 0;
this.vScrollBar.Maximum = 0;
if (control == null)
return;
if (control.Width > this.panel.ClientSize.Width)
{
this.hScrollBar.LargeChange = this.panel.ClientSize.Width;
this.hScrollBar.Maximum = control.Width;
}
if (control.Height > this.panel.ClientSize.Height)
{
this.vScrollBar.LargeChange = this.panel.ClientSize.Height;
this.vScrollBar.Maximum = control.Height;
}
}
void UpdateScrollPosition()
{
if (this.Control == null)
return;
if (this.hScrollBar.Maximum != 0)
{
Int32 x = (Int32)Math.Round(
(this.hScrollBar.Value / (Double)this.hScrollBar.Maximum) *
(this.hScrollBar.Maximum - this.hScrollBar.LargeChange)
);
if (x >= 0)
this.Control.Left = -x;
}
if (this.vScrollBar.Maximum != 0)
{
Int32 y = (Int32)Math.Round(
(this.vScrollBar.Value / (Double)this.vScrollBar.Maximum) *
(this.vScrollBar.Maximum - this.vScrollBar.LargeChange)
);
if (y >= 0)
this.Control.Top = -y;
}
}
private void ScrolledWindow_Paint(object sender, PaintEventArgs e)
{
Rectangle r = new Rectangle(
0,
0,
this.Width - this.vScrollBar.Width - 2,
this.Height - this.hScrollBar.Height - 2
);
Motif.ControlPaint.DrawBorder(e.Graphics, r, true);
}
private void panel_Layout(object sender, LayoutEventArgs e)
{
this.panel.Left = 2;
this.panel.Top = 2;
this.panel.Width = this.Width - this.vScrollBar.Width - 4;
this.panel.Height = this.Height - this.hScrollBar.Height - 4;
}
private void ScrolledWindow_Resize(object sender, EventArgs e)
{
Double hpct = 0;
Double vpct = 0;
if (this.hScrollBar.Maximum > 0)
hpct = this.hScrollBar.Value / (Double) this.hScrollBar.Maximum;
if (this.vScrollBar.Maximum > 0)
vpct = this.vScrollBar.Value / (Double) this.vScrollBar.Maximum;
UpdateScrollBars();
this.hScrollBar.Value =
(Int32) Math.Round(hpct * this.hScrollBar.Maximum);
this.vScrollBar.Value =
(Int32) Math.Round(vpct * this.vScrollBar.Maximum);
UpdateScrollPosition();
}
private void vScrollBar_ValueChanged(object sender, EventArgs e)
{
UpdateScrollPosition();
}
private void hScrollBar_ValueChanged(object sender, EventArgs e)
{
UpdateScrollPosition();
}
}
}
There is one problem: when I add 3000 lines of text to a Label
, the scrolling doesn't work properly. I think it has to do with the fact that the Top
property of the control becomes < 2^16.