(Print this page)

The StatusBar control
Published date: Wednesday, September 1, 2004
On: Moer and Éric Moreau's web site

I have seen a lot of programmers putting a StatusBar control to their main form and never using it. Are you one of these? In this column, I’ll show you some nice features (icons, back color setting, buttons, combos, progress bar) you can add to a StatusBar, that are requiring some code (but not that much).

The basics

By default, the StatusBar presents a single panel at the bottom of form. Do you know that a status bar can be located elsewhere? Since it is a regular control, it has the Dock property.

SizingGrip is a property specific to the StatusBar. When this property is set to True, the StatusBar displays small diagonal lines at the lower-right corner of the control to cue the user that the form is resizable (notice that you have to set it – this property is not automatically set depending on the FormBorderStyle property).

This default appearance looks more like a label then anything else. To set a value to this label-replacement, just set the Text property:

StatusBar1.Text = “Hello World!”

Instead of using the StatusBar as a replacement for the Label control, we typically set the ShowPanels property to True. Instead of having one large label, we now have has many panels as we want.

Panels can be created at design-time or at run-time (like much anything else in .Net). There is not much to do with the designer of this control. Yes you can add panels but features are quite limited from the “StatusBarPanel Collection editor”.

Figure 1: The StatusBarPanel Collection editor

Each panel has some properties, to which you are surely used to (like Alignment, Text, Width, …) so I want explained them. Other properties are more specific to the panels:

  • AutoSize: this property can take one of three values: 
  •         None: the panel will never resize 
  •         Contents: the panel will be resize depending on its content 
  •         Spring: the panel will share the remaining space left off on the status bar 
  • MinWidth: this property ensure a minimum width for a spring panel 
  • Style: this property has two values 
  •         Text: to display plain text or icon only 
  •         OwnerDraw: for all other cases (I bet you will often set this property!). You then need to provide code to draw the content of your panel.

Those who read me regularly know that I’m not a big fan of setting properties at design time. So as usual, I will use code to create my panels and everything else.

The demo application

As advertised in the introduction, we will customize a status bar so it will look like this.

Figure 2: The demo application

I will not copy the complete source code here (only important parts) so I invite you to use the link at the bottom of this column to download the demo application.

Adding an Icon

Lets start with an easy task: adding (and removing) an icon from a panel of the status bar. First we need to add a new panel. This code is normally executed when a form loads. So this code creates a new instance of the StatusBarPanel class, set some properties (no trace of the icon yet) and add it to the status bar (I suppose you have a status bar named StatusBar1 with no properties set on your form).

Dim pnlX As StatusBarPanel

With StatusBar1
    'Icon panel
    pnlX = New StatusBarPanel
    With pnlX
        .Alignment = HorizontalAlignment.Center
        .AutoSize = StatusBarPanelAutoSize.None
        .BorderStyle = StatusBarPanelBorderStyle.Sunken
        .Width = 30
    End With
    .Panels.Add(pnlX)

    .SizingGrip = True
    .ShowPanels = True
End With
Instead of just displaying an icon, I will use a button to toggle the setting of the icon. So this is the code I put in a button:
If btnImage.Tag Is Nothing Then
    btnImage.Tag = ""
End If

If btnImage.Tag.ToString = "IMAGE" Then
    StatusBar1.Panels(MyPanels.pnlIcon).Icon = Nothing
    btnImage.Text = "Display Icon"
    btnImage.Tag = ""
Else
    StatusBar1.Panels(MyPanels.pnlIcon).Icon = _
            New System.Drawing.Icon("c:\W95MBX01.ICO")
    btnImage.Text = "Hide Icon"
    btnImage.Tag = "IMAGE"
End If
Of the 12 lines, we have here, only 2 are really setting the icon (1 to display – 1 to erase). The others are only to keep track of the state and display or hide the icon from the panels. If you ask yourself what “MyPanels.pnlIcon” is, it is simply an enumeration that I use to specify the index of the panel I want to affect.

Changing the back color of a panel

You may want to change the back color of one of the panel as a visual cue for the user. Since there is no property for this, we have to find another method to handle it.

Once again we need some code to add a panel to host it:

pnlX = New StatusBarPanel
With pnlX
    .AutoSize = StatusBarPanelAutoSize.None
    .BorderStyle = StatusBarPanelBorderStyle.Sunken
    .Style = StatusBarPanelStyle.OwnerDraw
    .Text = "Colored text"
    .Width = 100
End With
.Panels.Add(pnlX)
This time, the Style property is set to OwnerDraw. This means that every time the panel will need to be painted, an event of the status bar called DrawItem will be raised. You have to add code to this event to handle it. The code looks like this:
sbdevent.Graphics.FillRectangle(New SolidBrush(mclrSelectedColor), sbdevent.Bounds)

Dim sf As New StringFormat
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center

sbdevent.Graphics.DrawString(sbdevent.Panel.Text, _
                             StatusBar1.Font, _
                             New SolidBrush(Color.Black), _
                             New RectangleF(sbdevent.Bounds.X, _
                                            sbdevent.Bounds.Y, _
                                            sbdevent.Bounds.Width, _
                                            sbdevent.Bounds.Height), _
                             sf)
This code creates a rectangle with some color (selected elsewhere on the click of a button using a color dialog) in it and also redraws the text that is appearing into the panel. Yes we have to redraw everything. Remember, the style is OwnerDraw!

Detecting a click on a panel

You may want to detect when a user click on a particular panel of your status bar. For panels like the 2 we just added, there are no problems. The PanelClick event of the status bar is raised for that. For panels that will host controls (buttons, combos, …) it is more difficult because the click event of the control is raised (instead of the PanelClick event). One of the arguments sent by the PanelClick event lets us determines which panel was clicked:

Select Case StatusBar1.Panels.IndexOf(e.StatusBarPanel)
    Case MyPanels.pnlIcon
        MessageBox.Show("Panel = Icon")
    Case MyPanels.pnlProgressBar
        MessageBox.Show("Panel = Progress bar")
    Case MyPanels.pnlTextColor
        MessageBox.Show("Panel = Text Color")
    Case MyPanels.pnlProgressPanel
        MessageBox.Show("Panel = Another Progress bar")
End Select

Hosting a Button control

Typically, a status bar is passive control but nothing stops you from having it accepting user interaction. In this example, we will add a button to a panel of the status bar. First, we need to add the panel and its button:

pnlX = New StatusBarPanel
With pnlX
    .AutoSize = StatusBarPanelAutoSize.None
    .BorderStyle = StatusBarPanelBorderStyle.Sunken
    .Style = StatusBarPanelStyle.OwnerDraw
    .Width = 100
End With
.Panels.Add(pnlX)
Me.Controls.Add(mctlButton)
mctlButton.Text = "Try me!"
mctlButton.Parent = StatusBar1
AddHandler mctlButton.Click, AddressOf ButtonClickTest
Notice that the Style property is once again set to OwnerDraw (meaning that some other code is coming soon!). Also notice that a button is added to the form’s controls collection (mctlButton is declared as private at the class level). The Text and the Parent properties of the button are set. Finally, an event handler is added because we want the button to react when the user clicks on it. When the button is clicked, the ButtonClickTest method will be called so it means that you need to create this method and because it is called as an event, it must have an event signature like this:
Private Sub ButtonClickTest(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs)
    MessageBox.Show("Are you really surprised to see me!!!")
End Sub
The only thing that is missing now is the code that will draw the button on the panel. When you are hosting existing controls on a panel, you simply need to set location and size of this control (coordinates are given by one of the DrawItem event named sbdevent) and the control will paint itself:
Case MyPanels.pnlButtton
    With mctlButton
        .Location = New Point(sbdevent.Bounds.X, sbdevent.Bounds.Y)
        .Size = New Size(sbdevent.Bounds.Width, sbdevent.Bounds.Height)
        .Show()
    End With

Hosting a ComboBox control

Hosting a combo or almost any other control is not different then a button like we did in the previous example so I will not copy the code here (but you have a working example in the downloadable demo).

Hosting a progress bar control

How hosting a progress bar is different from a button? From the initialization and the drawing of the control, there are no differences. The difference comes from the fact that the panel needs to be repainted on some events like the tick of a timer to indicate a new progress value. So here is the code I have in the Tick event of a timer control:

If mctlProgressBar.Value < 10 Then
    mctlProgressBar.Value += 1
    StatusBar1.Invalidate()
Else
    Timer1.Enabled = False
End If
You may have seen the StatusBar1.Invalidate() call. This line will cause the status bar to be repainted and the progress bar to show its new value by the same time.

Another way of showing the progress

The downloadable demo proposes another method of displaying a progress bar in a panel that does not require the use of the ProgressBar control. Its surface is also a solid block and even the percentage is written into the panel.

Conclusion

Implementing a useful StatusBar requires some code but finally we can use it for more then a label replacement.

If anyone is making a subclass control from it, please send it to me!

I hope you appreciated the topic and see you next month.


(Print this page)