Most programs that use document files maintain a list of most-recently-used files. This list, often called an MRU list, is dynamically created by the application as the user works with document files.
VCL doesn’t provide built-in support for an MRU list. So, in order to implement an MRU list in your C++Builder applications, you’ll have to do it yourself. In this article, we’ll show you how to create and maintain such a list. You’ll use the Registry to store the list of files (see "Putting the Registry to Work," on page 8, for details on using the Registry). Along the way, we’ll introduce some of the features of the TMenu class.
First, the list shouldn’t be visible when you run the application for the first time. In other words, if there are no recently used files, then the list should be hidden. As files are opened, they’ll appear in the list. Typically, an MRU list will have a maximum number of entries--generally five to ten. The list should be maintained on a first-in, first-out (FIFO) basis so the last file used is always at the top of the list. Once the list grows to the maximum size, the oldest file is removed to make room for the newest file.
Typically, the MRU list shows the complete path and filename of the document file. The list usually associates a hotkey with each menu item. For example, the hotkey for the first item is usually 1, the hotkey for the second item is 2, and so on. The MRU list should appear on the File menu, just above the Exit menu item.
Not every application uses this exact approach, but such a layout seems to be the most widely used. (Note that C++Builder uses a different scheme; it implements the MRU list on a pop-up menu called Reopen. I don’t particularly like this non-standard approach, and I wouldn’t write my own applications using this method.) Some applications place the MRU list at the very end of the File menu, which is a slight variation. In any case, the decision of where to put the list is up to you. Now that you know how the MRU list should look and act, you can get to work on the design stage.
A way to show the MRU list when it’s needed and hide it when it isn’t
| A way to detect when an MRU menu item
is clicked
| An event handler that performs some action when an MRU item is clicked
| A routine to manage the list (adding new items and deleting old items)
| A place to store the filename strings while the program is running
| A more persistent place to store the filename strings between application instances
| |
Begin by dropping a MainMenu component on a blank form. Then, double-click the MainMenu icon to start the C++Builder Menu Designer. To quickly insert a pre-built File menu into the main menu, use the Insert From Template option. Now, create a separator just above the Exit item separator (type a dash in the menu item’s Caption property and press [Enter]) and change its Name property to MRUSeparator. Create five menu items under the separator and name them MRU1 through MRU5. You can leave the Caption property blank, since the menu item text will be supplied at runtime. Select all five blank menu items and set the OnClick event handler to MRUClick--doing so will allow all the menu items to use the same OnClick event handler. Set the Visible property for the separator and the blank menu items to False.
Now, close the Menu Designer and test the menu. It should look like a regular File menu, since the MRU list is initially hidden. But at what point do you set the Visible property to True? Once again, it’s VCL to the rescue. You can create an OnClick handler for the top-level File menu. This OnClick event will give you a chance to modify the menu before it’s displayed--a perfect time to show the necessary MRU menu items. You can’t set up the event handler for the MRU click events at this point, because you haven’t yet determined how you’re going to store the filenames. Let’s take a look at that issue now, then return later to the OnClick event handler.
As an added benefit, the TStringList gives you a convenient way to implement the FIFO aspect of the MRU list. When the user opens a new file, you’ll add the filename to the top of the list and delete the last item if necessary. For example,
const int maxItems = 5;
...
if (OpenDialog1->Execute()) {
// Open a file, etc.
// Add the filename to the MRU list.
MruList->Insert(0, OpenDialog1->FileName);
// Remove the last item if the list is full.
if (MruList->Count == maxItems + 1)
MruList->Delete(maxItems);
}
is all the code required to maintain a FIFO list of filenames--slick and easy. Naturally, you’ll create your string list in the form’s OnCreate event handler and delete it in the OnDestroy event handler.
void __fastcall
TForm1::MRUClick(TObject *Sender)
{
// cast Sender to a TMenuItem*
TMenuItem* itemClicked =
dynamic_cast(Sender);
// cCalculate 0-based index from there
int index =
itemClicked->MenuIndex - MRU1->MenuIndex;
// MruList[index] is the string we’re after
String FileName = MruList[index];
// do something with FileName
}
You can use similar logic to determine which of the MRU menu items to make visible. See Listing A for details.
As you open each file, its filename will be added to the MRU list. Close and restart the program, and you’ll see that the MRU list is persistent between application instances. When you click on one of the MRU items, the file corresponding to that menu item will then be loaded in the Memo component.
Listing A: MRUUNIT.H
//---------------------------------------------------- #ifndef MruUnitH #define MruUnitH //---------------------------------------------------- #includeListing B: MRUUNIT.CPP#include #include #include #include #include #include //---------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TMainMenu *MainMenu1; TMenuItem *File1; TMenuItem *New1; TMenuItem *Open1; TMenuItem *Save1; TMenuItem *SaveAs1; TMenuItem *N2; TMenuItem *Print1; TMenuItem *PrintSetup1; TMenuItem *N1; TMenuItem *Exit1; TMenuItem *MRUSeparator; TMenuItem *MRU1; TMenuItem *MRU2; TMenuItem *MRU3; TMenuItem *MRU4; TMenuItem *MRU5; TMemo *Memo1; TOpenDialog *OpenDialog1; TCheckBox *CheckBox1; void __fastcall MRUClick(TObject *Sender); void __fastcall File1Click(TObject *Sender); void __fastcall FormCreate(TObject *Sender); void __fastcall FormDestroy(TObject *Sender); void __fastcall Open1Click(TObject *Sender); private: // User declarations TStringList* MruList; public: // User declarations __fastcall TForm1(TComponent* Owner); }; //---------------------------------------------------- extern TForm1 *Form1; //---------------------------------------------------- #endif
//---------------------------------------------------- #include#pragma hdrstop #include "MruUnit.h" //---------------------------------------------------- #pragma resource "*.dfm" const int MruCount = 5; const char* RegKey = "Software\\BCBJournal\MruTestProgram"; TForm1 *Form1; //---------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //---------------------------------------------------- void __fastcall TForm1::MRUClick(TObject *Sender) { // Load a file in the Memo component based on // the MRU item that was clicked. TMenuItem* itemClicked = dynamic_cast (Sender); int index = itemClicked->MenuIndex - MRU1->MenuIndex; Memo1->Lines->LoadFromFile( MruList->Strings[index]); } //---------------------------------------------------- void __fastcall TForm1::File1Click(TObject *Sender) { // This is the OnClick handler for the top // level File menu. Here we check each string // in the MRU list and show the associated MRU // menu item if the string is not empty. int index = MRU1->MenuIndex; for (int i=0;iStrings[i] != "") { MRUSeparator->Visible = true; File1->Items[index + i]->Visible = true; char buff[MAX_PATH]; sprintf(buff, "&%d %s", i + 1, MruList->Strings[i].c_str()); File1->Items[index + i]->Caption = buff; } } } //---------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { // Create the TStringList class. MruList = new TStringList; // Load the strings from the registry. First // try to open the key. If that fails, then we // know that the program is being run for the // first time and that we need to create the // key and set up the MRU items in the key. TRegistry* reg = new TRegistry; if (!reg->OpenKey(RegKey, false)) { reg->OpenKey(RegKey, true); reg->WriteString("MRU1", ""); reg->WriteString("MRU2", ""); reg->WriteString("MRU3", ""); reg->WriteString("MRU4", ""); reg->WriteString("MRU5", ""); } // Read each string from the registry. Some of // the strings could be empty, but that’s OK. MruList->Add(reg->ReadString("MRU1")); MruList->Add(reg->ReadString("MRU2")); MruList->Add(reg->ReadString("MRU3")); MruList->Add(reg->ReadString("MRU4")); MruList->Add(reg->ReadString("MRU5")); // Delete the TRegistry object. delete reg; } //---------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { // Save the string list to the registry. If // the check box is checked, then delete the // key and don’t save the MRU list (duh). TRegistry* reg = new TRegistry; if (CheckBox1->Checked) reg->DeleteKey(RegKey); else { reg->OpenKey(RegKey, true); // Write the list to the registry. reg->WriteString( "MRU1", MruList->Strings[0]); reg->WriteString( "MRU2", MruList->Strings[1]); reg->WriteString( "MRU3", MruList->Strings[2]); reg->WriteString( "MRU4", MruList->Strings[3]); reg->WriteString( "MRU5", MruList->Strings[4]); } // Clean up. delete reg; delete MruList; } //---------------------------------------------------- void __fastcall TForm1::Open1Click(TObject *Sender) { // Load the selected file into the Memo and // update the MRU list. Add the FileName to // the top of the string list and delete the // last item if the string list is full. We // don’t check for duplicate strings, but that // would be a good feature to implement. if (OpenDialog1->Execute()) { Memo1->Lines->LoadFromFile( OpenDialog1->FileName); MruList->Insert(0, OpenDialog1->FileName); if (MruList->Count > MruCount) MruList->Delete(MruCount); } } //----------------------------------------------------