Skip to content

Create a “Recent Items” Menu

It’s helpful and common these days to add a “Recent Items” menu that tracks the last opened files or other recent searches among other things. This blog post will teach you one of the multiple approaches to do this in Xojo.

Once you create a new Xojo Desktop project the first thing we need to do is to add a new class to it because this will be a dynamically created submenu. This will be a subclass of the MenuItem class, so we can implement the Action Event Handler. This event is the one that will be fired every time the user selects an entry among those listed in the “Recent Items” submenu from the main File menu.

So, add a new class to the project using the following data in the Inspector Panel:

  • Class Name: MyMenuItem
  • Super: MenuItem

Then, add a new Delegate to the subclass using the following data in the Inspector Panel:

  • DelegateName: MenuItemCallBack
  • Parameteres: Caption As String, Value As String
  • Scope: Public

Next, add a new property to the class using this data in the Inspector Panel:

  • Name: pMenuItemCallback
  • Type: MenuItemCallBack
  • Scope: Public

The purpose of this property is to store the address of the method that will be invoked when the user selects a given menu item based on this class (i.e.: a recent item from the “Recent Items” submenu).

The last thing to do in our subclass definition is adding the Action Event Handler. Once added, simply write this line of code in the resulting Code Editor:

if pMenuItemCallback <> nil then pMenuItemCallback.Invoke(me.Text, me.Tag)

As you can see, it limits itself to invoke the method pointed by the pMenuItemCallback property passing along both the Text and Tag properties of the instance. In our example, the Text will be the file name, while the Tag property of the MenuItem will store the NativePath pointing to the real file on the original drive.

With our subclass completed, the second step is to provide a way to create new MyMenuItem instances and add them to the desired menu item. In order to keep things simple, we will include a button in the user interface of the app that when clicked will let the user select any file, creating a new MyMenuItem instance that will reference that file and that will be added to the File submenu on the main menu bar.

In addition, we will keep track of the added files in an Array of Strings.

So, select the Window1 window in the project to access the Layout Editor and add a new Button to it from the Library using the following data in the Inspector Panel:

  • Name: AddItem
  • Caption: Add Recent Item

Then, add the Action event to the just added button and write the following code in the associated Code Editor:

// Display a dialog so the user can choose a file
Var f As FolderItem = FolderItem.ShowOpenFileDialog("")

If f <> Nil Then

  // Add the name and the nativepath of the selected file, separated with the Tab character
  app.RecentItemsCollection.AddRow(f.DisplayName + Chr(9) + f.NativePath)

  // Create a new menu item Instance from the MyMenuItem class, setting the name, tag
  // and the property with the method callback.
  // Then add the new menu item to the FileRecentItems submenu.

  Var mi As New MyMenuItem
  mi.Text = f.DisplayName
  mi.Tag = f.NativePath
  mi.pMenuItemCallback = WeakAddressOf app.recentItemCallback
  FileRecentItems.AddMenu(mi)

End If

As you can see here, we need to create the “RecentItemsCollection” array property under the App Object, and make sure that we name the File submenu “FileRecentItems”.

Select the App object and add a new property to it named “RecentItemsCollection() As String”. Keep the Scope as Public.

Next, select the MainMenuBar item in the Project Navigator so the Menu Editor is Displayed. Then, click on the File menu and add a new Menu item clicking on the fourth button from the right in the editor toolbar. That action will add a new menu item under the File menu.

Use the Inspector panel to set the following values in the just added menu item:

  • Name: FileRecentItems
  • Text: Recent Items
  • Submenu: Enabled

Enabling all the Recent Items

We would want that the “Recent Items” menu and all the items under it are enabled by default every time the user clicks on the File menu. For this, we need to add the EnableMenuItems Event Handler to the App object. This is the event that will be fired every time the user clicks on an item in the menu bar, giving us an opportunity to decide which one should be displayed as enabled or not.

Once the EnableMenuItems has been added, write the following code in the associated Code Editor:

FileRecentItems.Enabled = True

For n As Integer = 0 To FileRecentItems.LastRowIndex

  FileRecentItems.MenuAt(n).Enabled = True

Next

As you can see, we are enabling the FileRecentItems and all the items that may have been added to this submenu. For that we only need to iterate over all the descendant items and access them using the MenuAt method to set their Enabled property to True.

Loading and Saving Recent Items

By now, we know how to create new menu items and add them dynamically to the expected submenu; and even how to enable them when the user clicks on the menu bar. But some important pieces are left. First of all, we still need to add the RecentItemsCallback to the App object, because this is the method we assign as the delegate to every new MyMenuItem instance (that is, the method that will let us know the recent item that has been selected by the user).

So, select the App object in the Project Browser and add a new Method to it using these values in the Inspector Panel:

  • Method Name: RecentItemCallback
  • Parameters: Caption as string, Value as String
  • Scope: Public

Then write this line of code in the Associated Code Editor:

MessageBox "Recent Item Selected" + endofline + endofline + "Selected item: " + Caption + EndOfLine + endofline + "File Path: " + Value

As you can see, our example project will limit to display a dialog with the name of the selected item and the underlying native path to the real file. You may want to do something more interesting in your own projects using these pieces of information. For example, open a new document or the selected file in order to edit it!

But none of this of this is very useful if you are not able to save and later retrieve these entries every time you run this example app (or your own apps!). So let’s take care of it now.

Let’s take care of saving the current “Recent Items” to disk. We will do that using a regular text file stored in the path pointed by SpecialFolder.ApplicationData; so that will assure us that the file is going to be saved in the proper place on every supported OS (even if you intend to distribute your solutions as sandboxed apps). Of course, you can opt to save this information as JSON, XML or in a database; in that case, you should modify the provided code in order to parse and process the data acordingly to your format of choice.

We will save our Recent Items when the user closes the app, so it makes sense to add the code to the Close Event Handler in the App object. Once added, write the following snippet of code in the associated Code Editor:

Try
  Var f As FolderItem = SpecialFolder.ApplicationData.Child("RecentItems")

  If f.Exists Then f.Remove

  Var tos As TextOutputStream = TextOutputStream.Open(f)

  For Each item As String In RecentItemsCollection
    tos.WriteLine item
  Next

  tos.Close

Catch e As IOException
  MessageBox e.Message
End Try

As you can see, there is nothing exceptional in this code. It simply creates a TextOutPutStream on the “RecentItems” folderItem (stored in the path pointed by ApplicationData), saving every entry stored in the RecentItemsCollection, line by line.

How can we retrieve this information, recreating the menu items the next time the user runs the app? This is something we are going to do in the Open Event Handler of the App object. Select it in the Project Browser and write this piece of code in the associated Code Editor:

Var f As FolderItem = SpecialFolder.ApplicationData.Child("RecentItems")

If f <> Nil And f.Exists Then

  Try
    Var tis As TextInputStream = TextInputStream.Open(f)
    Var recentItems() As String = tis.ReadAll.ToArray(EndOfLine)
    Var mi As MyMenuItem

    For Each item As String In recentItems
      app.RecentItemsCollection.AddRow item

      If item <> "" Then
        mi = New MyMenuItem
        mi.Text = item.NthField(Chr(9),1)
        mi.Tag = item.NthField(Chr(9),2)
        mi.pMenuItemCallback = WeakAddressOf RecentItemCallback

        FileRecentItems.AddMenu(mi)
      End If

    Next

    tis.Close

  Catch e As IOException
    MessageBox e.Message
  End Try

End If

As you can see, we are doing the opposite opperation; pointing the folder item to our “RecentItems” file under the ApplicationData directory and opening it as a TextInputStream. Then, we create a temporary array with all the file contents using the EndOfFile character as the separator for each new array entry.

Then we iterate the temporary array to recreate the menu items and add them to the FileRecentItems submenu. It’s that simple!

Summary

This is one way you can implement a “Recent Items” submenu in your desktop apps. Of course, there is much room for improvement. For example, you can check if an item has been added already to the Recent Items submenu (hint: maybe you want to use the RecentItemsCollection array for that), and even limit the maximum number of items displayed in the Recent Items menu.