Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Motif scroll bars

0.00/5 (No votes)
21 Feb 2008 1  
Motif style scroll bars implemented from scratch.

Sample Image - screenshot.jpg

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;

            // Update LargeChange and Maximum. We do not set Maximum to
            // the number of pixels we can scroll but to the entire
            // width or height of the control to scroll so that we
            // don't have to deal with fractions.

            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;

            // Calculate scroll position. Map current scroll value from
            // [0, Maximum] to [0, Maximum - LargeChange], that is the
            // actual number of pixels we can scroll.

            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)
        {
            // The window size has changed. The size of the scroll bars
            // have changed too. Map the scroll values from [0, Maximum]
            // to new [0, Maximum].

            Double hpct = 0;
            Double vpct = 0;

            // Old scroll values in percent.

            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;
            
            // New Maximum.

            UpdateScrollBars();

            // New scroll values.

            this.hScrollBar.Value = 
              (Int32) Math.Round(hpct * this.hScrollBar.Maximum);
            this.vScrollBar.Value = 
              (Int32) Math.Round(vpct * this.vScrollBar.Maximum);

            // Done. Update scroll position of this.Control.

            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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here