Introduction
I believe there's an old saying, "Clothes maketh the man", and this definitely applies to software too. No matter how clever and hard-working your applications are, if they're not wearing the latest well-designed clothes, people will think they are poorly written and out of date.
While working on a new project recently, I did a quick search on the web to see what the competition was up to. One of the features they had, which I didn't, was an Outlook-style menu bar, commonly referred to simply as an Outlook Bar. Deciding I wanted one too, I thought about writing my own. Searching the Code Project, I found two useful articles, Marc Clifton's An Outlook Bar Implementation, and Outlook XP bar by ACorbs. Both these articles were interesting and thought-provoking, and I downloaded each of them for a bit of a poke around, as one does!
I then found myself in a bit of a dilemma. I didn't want to simply copy someone else's code, but at the same time, I didn't want to spend hours and hours reinventing the wheel. I remembered a comment made by my manager at a previous job, many years ago. "What are we trying to do here? Achieve technical excellence, or get a job done?" Realising that Marc and ACorbs had done the former, I decided to go for the latter approach, and started doing some lateral thinking.
The eureka moment!
I proceeded to rummage around in the standard Windows Forms toolbox, and began to idly play around with splitters and panels. While doing this, I came across the Dock
property, which specifies the justification for a control. In other words, does it attach itself to the top, bottom, left or right of its container, or does it fill all the available space. An idea occurred to me, so I added a Panel
to my form, and added three Buttons
to the panel. I set the Dock
property of each button to Top
, and "Lo!", I had created an Outlook Bar!
Well, almost...
Obviously, all I'd really done was stack three buttons one on top of the other, but they did resize themselves with the panel, which was decent of them. At this point, I realised I was going to have to write some code.
Some code
The only code I really needed is in the Click
event handler for the buttons. They all share a common handler, as shown here:
void ButtonClick(object sender, System.EventArgs e)
{
Button clickedButton = (Button)sender;
int clickedButtonTabIndex = clickedButton.TabIndex;
foreach (Control ctl in panel1.Controls)
{
if (ctl is Button)
{
Button btn = (Button)ctl;
if (btn.TabIndex > clickedButtonTabIndex)
{
if (btn.Dock != DockStyle.Bottom)
{
btn.Dock = DockStyle.Bottom;
btn.BringToFront();
}
}
else
{
if (btn.Dock != DockStyle.Top)
{
btn.Dock = DockStyle.Top;
btn.BringToFront();
}
}
}
}
switch (clickedButton.Text)
{
case "Cars":
CreateCarList();
break;
case "Outlook Shortcuts":
CreateOutlookList();
break;
case "Zip Files":
CreateZipList();
break;
case "Miscellaneous":
CreateMiscList();
break;
}
listView1.BringToFront();
}
Basically, when any of the buttons on the panel is clicked, we save the clicked button and its TabIndex
, then go through all the controls on the panel in turn. If it's a button, we check the TabIndex
against that of the clicked button. If the TabIndex
is greater, then the current button must be below the clicked one, so we send it to the bottom by setting its Dock
to DockStyle.Bottom
. If the TabIndex
is lower, then the button goes to the top.
At this stage, I should point out that to make this work, it's vital that the TabIndices of the buttons are set at design time to the correct order. In the supplied example, the "Cars", "Miscellaneous", "Zip Files" and "Outlook Shortcuts" buttons have their TabIndex
set to 1, 2, 3 and 4 respectively.
Back to the main plot. Having set the Dock
properties to send the buttons up and down, I noticed that all was not well. If I had buttons 1, 2, 3 and 4 at the top, and I clicked button 1, then buttons 2 to 4 went to the bottom, but they ended up in reverse order, like this:
In an attempt to correct this, I tried a few things, such as walking the panel's controls in reverse order, and altering the TabIndex
values "on the fly", but without success. I decided to research the Dock
property on MSDN, which told me about the z-order, and explained that it could be controlled by the SendToBack
and BringToFront
methods of a control. It's explained here and here, much more clearly that I can do in a few words. Suffice to say, by calling the SendToBack
method on each button after I docked it, preserved the order of the buttons.
Finally...
The last piece of the jigsaw was another standard Windows Control, the ListView
. I added this to the container panel, left its View
property as the default LargeIcon
, and set its Dock
property to Fill
, which means it will fill all the remaining space left by the buttons. I added some ImageList
s, threw together some code to populate the ListView
depending on which button was clicked, and I had a nice looking, functional Outlook bar, created from standard controls and a few lines of code. I then added a second ListView
to provide some basic functionality to demonstrate that the menu bar worked.
Hopefully, this article will be of interest, and possibly show how sometimes it's useful to look at a problem from a few different angles before getting your code editor out!