September 1997

Sprucing up your status bars

by Kent Reisdorph

If you write applications, then it probably won't be long before you write one that uses a status bar. The basic status bar sits at the bottom of the screen and displays hint text or status information. More sophisticated status bars show key states, cursor position, mouse coordinates, the current time, or other application-specific information. A good application makes proper use of a status bar when and where appropriate, and a status bar can spruce up an otherwise boring user interface. In this article, we'll show how you can add a little pizzazz to your status bars. You'll learn how to create multiple-panel status bars and how to add a clock to your status bar. As an added bonus, we'll show you how to put a progress bar on your status bar.

Status bar basics

The StatusBar component encapsulates the Win32 status bar control. Developers who must support 16-bit operating systems dream wistfully of the day when they'll be able to use the StatusBar component (it's available only in 32-bit programs). But don't worry; you can just drop a StatusBar component on your form and hit the ground running. The StatusBar component provides the following features:
bulletA single panel or multiple panels
bulletText displayed in panels
bulletOwner-drawing of panels
bulletA grab bar for easy form sizing
When you drop a StatusBar component on your form, you'll notice that it immediately aligns itself along the bottom of the form, as shown in Figure A.

Figure A: The status bar automatically docks itself along the full width of the bottom of your form. Figure A

VCL makes some assumptions about your design, and in most cases the bottom of the form is exactly where you want your status bar to be. The status bar initially includes a single panel, and the sizing grip appears in the lower-right corner. You'll notice that if you size the form, the status bar also resizes--it always takes up the entire width of the form. These features are courtesy of the Align property, which is set to alBottom by default. The SizeGrip property determines whether the sizing grip appears in the lower-right corner of the status bar. This feature simply makes it easier to size the window on which the status bar resides--its presence doesn't give the user any added functionality, nor does its absence prevent the user from sizing the window. For more on status bar basics, see "Simple or Complex Status Bars?"

Displaying hints in the status bar

Displaying hint text is one of the primary uses for a status bar. The global Application object has an event called OnHint , which is fired whenever the mouse cursor passes over a control whose ShowHint property is set to True. This event doesn't do anything unless you respond to the event. To respond to the OnHint event, you first need to create a function that will handle the event. Once you've created the function, you need to tell VCL to call your event handler when the OnHint event occurs. Finally, in your event handler, you can grab the hint text from the Application object's Hint property and display it in your status bar. That's a lot to absorb without breaking it down into chunks, so let's examine each step in more detail. To create an event handler for the OnHint event, you first need to declare the event-handling function in your main form's class declaration. Switch to your main form's header and type the following line in the private section:

void __fastcall MyOnHint(TObject* Sender);

Now, switch back to the source file for the main form and enter the following lines:
void __fastcall TForm1::MyOnHint( TObject* Sender) 
{ 
  StatusBar->SimpleText = Application->Hint; 
}
This code takes the text from the Hint property of the Application object and displays it in the SimpleText property of the status bar--assuming, of course, that you have on the form a status bar whose SimplePanel property is set to True. The application still won't display hint text at this point, however, because VCL doesn't know to call your event handler. So, you need to hook your event handler to the Application object's OnHint event--the event handler for the form's OnCreate event is generally a good place to perform this step. Double-click on your form's background, and C++Builder will create an event handler for the form's OnCreate event. Type the following line in the event handler:
Application->OnHint = &MyOnHint;
Now VCL will call your event handler any time the OnHint event occurs. Note that before you can see any hint text displayed in the status bar, you must include components that have their ShowHint property set to True and that have text in their Hint property. Further, the text you enter in the Hint property should be in two parts: the text that will appear in a pop-up window when the mouse cursor pauses over the component, and the text that your MyOnHint() function will display in the status bar. You separate the two parts using the pipe character ( | ). For example, let's say you have on your form a button that opens a file. You'll enter text like this for the button's Hint property:

Open a File|Opens a file for editing

You can omit either the long hint text or the short hint text if you prefer. For example, suppose you don't want the pop-up hint to show for the button, but you want the long hint text to appear in the application status bar. In that case, you'll enter the following text for the Hint property:
|Opens a file for editing
Notice that you still use the pipe to indicate that the text you've entered is for the long hint text. If you only want to use the short hint text, then you don't need the pipe.

Time, anyone?

You'll frequently see the current time displayed on the right side of an application's status bar. Fortunately, this effect is fairly easy to achieve. In order to simplify displaying the time, you should create a status bar with multiple panels; the last panel's text alignment should be right justified. (Use the StatusBar Panels Editor to set the properties for the status bar panel, as we explain in "Simple or Complex Status Bars?")

VCL provides a function that makes getting the current time easy: The Time() function returns the current time based on the regional settings that are in effect on the user's machine. Once you have the time, you need to convert it to a string and format it so that it appears in a standard time format. Again, you don't have to do much work, because VCL provides the FormatString() function. Let's look at a simple example to see how these functions work. The following code snippet gets the current time, formats it in 12-hour format, and stores the result to a String object:

TDateTime currentTime = Time();
String format = "h:nn ampm";
String timeString = currentTime.FormatString(format);
You can then display the formatted string in a status bar panel. For instance, if the current time is 8:30 p.m., the displayed string will be
8:30 PM
To display the time in military (24-hour) time, including seconds, use the time-string format of hh:nn:ss. (For a complete description of time formatting options, see the C++Builder online help or the SYSUTILS.PAS source file.) You can condense the time formatting code a bit by combining the previous statements on a single line. In fact, you can display the time in the status bar panel and format it all in one fell swoop, as follows:
StatusBar->Panels->Items[2]->Text = 
Time().FormatString("h:nn ampm    ");
This example assumes that the status bar panel used to display the time is the third panel (panel index 2). Now you know how to display the time, but we haven't talked about when to display the time. You'll likely want to display the time when the application first appears. The main form's OnCreate event is a good place for the initial time display. To continuously update the clock as time marches on (and it always does), you'll need a Timer component. Typically you'll set the timer's Interval property to 1000 so that the timer fires once per second (every 1000 milliseconds). Windows timers are notoriously inaccurate, but they're good enough for the purpose of updating a clock on the status bar. All you need to do is drop a Timer component on your form and then update the status bar in the Timer's OnTimer event. By the way, the sizing grip can get in the way when you display text in the last panel of the status bar, especially if you right-justify the time string. Be aware that you may need to pad the end of the time string with a few blank spaces--otherwise, the sizing grip will cut off the end of the time display.

Pilgrim's progress?

Many applications need to display a progress bar to show the progress of some lengthy process. In some cases, the status bar is the perfect place to show this kind of indicator. The most obvious example is a word processor that shows a progress bar when the user saves the current file. You can easily add such a feature to your C++Builder applications. VCL already provides the StatusBar and ProgressBar components--all you need to know is how to combine the two.

Placing a basic progress bar on a status bar requires creating an instance of a TProgressBar component at runtime. The progress bar is parented to the status bar so that it appears on top of the status bar. Before you can create the progress bar at runtime, you need to declare a TProgressBar pointer in your main form's class declaration. To do so, switch to your main form's header file and type the following declaration in the private section:

TProgressBar* ProgressBar;

The TProgressBar class is contained in COMCTRLS.HPP

Normally, you'd have to add the include for this header file in the form's header. This step isn't necessary here, as long as you place the status bar on the form before you create the progress bar (a logical sequence of events). Since both classes are in the same header, C++Builder automatically adds the include for COMCTRLS.HPP. You can now write the code to create the progress bar. Once again, the OnCreate event handler for the form is a good place for this step. (You could also place the code in the form's constructor.) The code to create a typical progress bar on a form might look like this:
ProgressBar = new TProgressBar(StatusBar);
ProgressBar->Parent = StatusBar;
ProgressBar->Left   = 2;
ProgressBar->Top    = 4;
ProgressBar->Height = 13;
ProgressBar->Width  = 200;
Notice that we use the name of the status bar as both the owner of the progress bar (in the progress bar's constructor) and the parent (when we assign the Parent property). Doing so ensures that the progress bar belongs to the status bar and appears on top of it. When you create a component at runtime, remember to supply the values for any properties you need to modify. In some cases you'll want the default values, and in other cases you'll need to supply different values. In this case, we supply the size and position of the progress bar but let the other properties remain at their default values. Note that the Left and Top property values are relative to the upper-left corner of the status bar, not of the form. At this point, the progress bar is ready to use. You can assign a value to the Position property just as you would if the progress bar were on your form. You can use this same technique (creating a component at runtime) to place any type of component on your status bar.

Not so fast, progress boy

There are a couple of other things you may want to consider before shipping your application. First, where are you going to display the progress bar? The code in the previous section places it in the first panel, which is where hint text usually appears. If you want to display the progress bar in a panel that will also display text, then you need to hide the status bar until you're ready to show it. For example, your code to display the progress bar might look like this:
ProgressBar->Visible = true;
// show progress
ProgressBar->Visible = false;
If you use this technique, remember to set the Visible property to False when you initially create the progress bar. If you've dedicated a panel to your progress bar, then you can leave the progress bar visible. Another problem involves aesthetics. The normal progress bar has a sunken 3-D appearance. While fine for most progress bars, this look might clutter up your status bar more than you like. For this reason, you might want to remove the progress bar's border. Your first thought will probably be to set the BorderStyle property to bsNone, as you would for other components--but the ProgressBar component doesn't have a BorderStyle property. To remove the border, you must drop back to the Windows API. The code looks like this:
long style = GetWindowLong(ProgressBar->Handle, GWL_EXSTYLE);
style &= ~WS_EX_STATICEDGE;
SetWindowLong(ProgressBar->Handle, GWL_EXSTYLE,style);
You first use GetWindowLong() to get the value that contains the extended styles for the component. You then remove the
WS_EX_STATICEDGE
style (which gives the progress bar its 3-D look). Finally, you reset the extended style using SetWindowLong(). Voila! The border is removed.

For example

Listings A and B contain a program that illustrates the concepts we've presented in this article. Listing A: SBARMAIN.H
//---------------------------------------------
#ifndef SBarMainH
#define SBarMainH
//---------------------------------------------
#include 
#include 
#include 
#include 
#include 
#include 
//---------------------------------------------
class TForm1 : public TForm
{
  __published:    // IDE-managed Components
    TStatusBar *StatusBar1;
    TButton *Button1;

    TTimer *Timer1;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall Timer1Timer(TObject *Sender);
  private:        // User declarations
    TProgressBar* ProgressBar;
    void __fastcall MyOnHint(TObject* Sender);
  public: // User declarations
    __fastcall TForm1(TComponent* Owner);
};

//--------------------------------------------
extern TForm1 *Form1;
//--------------------------------------------
#endif
Listing B: SBARMAIN.CPP
//---------------------------------------------
#include 
#pragma hdrstop
#include "SBarMain.h"
//---------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::FormCreate(
TObject *Sender)
{
  // Assign hint event to Application's OnHint.
  Application->OnHint = &MyOnHint;
  // Create progress bar, setting the status bar
  // as the parent and owner.
  ProgressBar = new TProgressBar(StatusBar1);
  ProgressBar->Parent = StatusBar1;
  ProgressBar->Left = 2;
  ProgressBar->Top = 4;
  ProgressBar->Height = 13;
  ProgressBar->Width =
  StatusBar1->Panels->Items[0]->Width - 4;
  // Hide progress bar.
  ProgressBar->Visible = false;
  // Change style to remove progress bar's
  // border. Must use API, since progress bar 
  // doesn't have a Border property.
  // First get current extended style. 
  long style = GetWindowLong(ProgressBar->Handle, GWL_EXSTYLE);
  // Remove the WS_EX_STATICEDGE style.
  style &= ~WS_EX_STATICEDGE;
  // Set extended style again.
  SetWindowLong(ProgressBar->Handle, GWL_EXSTYLE, style);
  // Show time in last panel in 12-hour format.
  int lastPanel = StatusBar1->Panels->Count - 1;
  StatusBar1->Panels->Items[lastPanel]->Text =
  Time().FormatString("h:nn ampm    ");
}
//---------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  // Show progress bar.
  ProgressBar->Visible = true;
  // A loop to display progress bar.
  for (short i=0;i<100;i++) {ProgressBar->Position = i;
    // Slight delay so it doesn't go too fast.
    Sleep(1);
  }
  // Hide progress bar again.
  ProgressBar->Visible = false;
  StatusBar1->Panels->Items[0]->Text = "Done!";
}
//---------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
  // Display time on each OnTimer event.
  // First find last panel. Count property
  // is undocumented but is there nonetheless.
  int lastPanel = StatusBar1->Panels->Count - 1;
  // Display current time using 12-hour format.
  // Add a little space to end so size grip
  // doesn't wipe out the end of the text.
  StatusBar1->Panels->Items[lastPanel]->
  Text = Time().FormatString("h:nn ampm    ");
}
//---------------------------------------------
void __fastcall TForm1::MyOnHint(TObject* Sender)
{
  // Set status bar text to value of the
  // TApplication::Hint property.
  StatusBar1->Panels->
  Items[0]->Text = Application->Hint;
} 
To run this program, place a StatusBar component, a Button component, and a Timer component on a blank form. Next, modify the status bar's Panels property and create three panels with widths of 250, 200, and 50. (The width of the last panel is immaterial, since it will be automatically sized when the form is sized.) Next, enter the code from Listings A and B that appears in color. When you run the program, the current time will appear in the last panel of the status bar. Click the button, and a progress bar similar to the one shown in Figure B will appear. 

Figure B: Our application's status bar displays the current time and a progress bar.c

 

cKent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.