A Smart Window Menu for Your App

Once a program grows beyond a trivial test of concept, most will need a 'Window' menu to be professional and complete. There are numerous implementations available various places in the forums and on the web, in commercial add-on products, and of course Xojo includes a sample implementation for reference.

Looking at the Xojo example program, you find a basic implementation without lots of bells and whistles. It is not highly encapsulated, it has code spread all around. In this example we find:

This scattering of code in many different places makes the behavior harder to ‘drop-into’ applications, harder to understand, and harder to extend and debug. Many of these issues could be addressed by refactoring the code, but there will still be a degree of “scattering” of code, and code added to each window.

Beyond that, there are significant deficiencies or bugs in the behavior and numerous user friendly features that are not implemented. One of the bugs is that when all windows have been minimized, one still shows the check-mark, but it may not be the last windows which previously was front-most before being minimized. (At least two issues here, one is that there is no visible window so none should have a check-mark, and subsequent ‘close’ operations with windows minimized, does not reliably close the last open window.)

The user-friendly behaviors one might want included in the Window menu could be ‘Minimize’, ‘Zoom’, ‘Bring All to Front’, ‘Tile Window to Left’, ‘Tile Window to Right’, ‘Replace Tiled Window’, and various ‘Move to ’. Also some applications include Tab navigation features too. I’m not so ambitious as to attempt to implement all of these, but ‘Minimize’, ‘Zoom’ and ‘Bring All to Front’ seem like very reasonable ones to include in a standardized implementation. The ‘Tile’ and ‘Move to’ functions are available, on macOS by a click on the Maximize window decoration, so probably not so important to implement in the Window menu.

My approach is to severely minimize the code YOU need to add to the App and to each Window, and to make as much of the desired behavior transparent and embedded behind the scenes.

My solution involves only 2 classes: (my classes are prefixed with LDS_)

1) LDS_DesktopWindowListable - a subclass of DesktopWindow that is 'Listable' in the Window menu.

2) LDS_DesktopWindowMenuItem - a subclass of DesktopMenuItem that works with LDS_DesktopWindowListable

LDS_DesktopWindowListable implements both shared and instance methods. It can serve as a base class for application window sub-classes which will all inherit the behaviors needed for the 'Window' menu.

During development a few framework/OS quirks were found which required that I implement additional elements to make it work correctly.

LDS_DesktopWindowListable

I created the new LDS_DesktopWindowListable sub-class of DesktopWindow.

I added 'Opening' and 'Closing' event handlers with code so that each window would automatically add itself to the Window menu when opened and remove itself from the window menu when closed.

I added event definitions for 'Opening' and 'Closing' so that my two handlers could raise the same events to sub-classes.

I added a shared property as a window counter to assist in keeping window title's unique.

I added shared methods 'getUniqueWindowTitle' and 'windowDoesExistWithTitle' to encapsulate checking open widows and making sure that a unique window title is used for newly created window.

During testing, a couple issues arose, we will deal with them one at a time.

First issue, if all windows are minimized, one still is front-most, according to DesktopApplication.Window(0). There is no way to know whether the window is minimized or not (at least no way I could find). So, how to solve this issue?

I implemented event handlers for 'Minimized' and 'Activated', and a member property to track whether the window is minimized or not.

I also added event definitions for 'Minimized' and 'Activated' and raised these events in my event handlers so that sub-classes could access the events as well.

Now I can track whether there is actually a visible window which is not minimized.

Second issue, if all windows are minimized, DesktopApplication.Window(0) is not necessarily the last window minimized (i.e. the last one that was front-most. Further, the order of windows in the DesktopApplication.Window() list does not accurately reflect the 'stacking' order the windows had as they were minimized. This makes bringing all windows to the front in the correct stacking order impossible without adding more to the class to track the actually proper stacking order.

I added a shared variable, WindowList(), to the LDS_DesktopWindowListable class. This list will ALWAYS represent the proper stacking order for the windows.

I added shared methods to LDS_DesktopWindowListable to manipulate the WindowList(). These included windowAdd() and windowRemove().

I also added shared methods to LDS_DesktopWindowListable to encapsulate the work required to maintain the window list when a window 'minimizes', 'activates', is 'added' or 'is removed'

Finally, the LDS_DesktopWindowMenuItem is interfaced with in the 'Opening' and 'Closing' event handlers to keep the 'Window' menu in-sync

This design allows for the window itself to drive behavior in the 'Window' menu when opened, closed or otherwise modified.

Most of the added methods and members are 'protected' but the WindowList() is public, as are methods available to determine whether a window 'isShowing' (i.e. is visible and not minimized) and a 'topWindow' method which will return nil if no windows are showing (even if they exist)

LDS_DesktopWindowMenuItem

I created LDS_DesktopWindowMenuItem as a sub-class of DesktopMenuItem.

This is a more complex class, but the key is that it can be added to a menuBar (due to limitations on the design and macOS, it can be added to ONLY ONE MENU BAR). Just add a menu, name it 'Window' and change the Super to LDS_DesktopWindowMenuItem. The default constructor will create the top-level menu-bar item.

Subsequently, LDS_DesktopWindowListable will access the static methods of the class to add and remove menu item entries for each window.

Also the system will automatically call the MenuBarSelected event handler which will properly prepare manage and display the menu and it's items. (applying check marks etc.)

Also the system will call the MenuItemSelected event handlers when a menu is selected, which will bring the selected window to the front.

This is where checking the behavior of 'Window' menus in other applications helps to inform design. Is there anything else to add here that will be portable and helpful?

I'll mention 5 things seen in the Window menu of various programs, and how I choose to handle them:

1) Minimize & Zoom menus

2) Tile Window to Left, Tile Window to Right and Replace Tiled Window

3) Move to screen xxx (on multi-monitor systems)

4) Bring All to Front

5) Check-mark replaced with Diamond for windows which are minimized

Let's start with #1 and #4. These can be easily implemented by giving the LDS_DestopWindowMenuItem a bit more split personality. This menuItem already knows that it is either the menuItem in the menuBar, or is associated with a specific window. So I added a 'SpecialMenuItemTypes' enumeration and member property so we can create special entries to handle those special commands.

Then I implemented some additional capacity in the MenuItemSelected event handler and Constructor to handle 'Minimize', 'Zoom', and 'Bring All to Front' functions.

Now looking at #2 and #3, well, These could be handled in a similar manner, but, since the maximize window decoration (green dot on macOS) has contextual menu options to handle these, I decided that they were not necessary to add to my Window menu.

Lastly, #5, diamond marks for minimized windows. This seemed like a good idea. After examining the desktopMenuItem class, I decided that the only way to do that was with an Icon. I made a Diamond icon (48x48, 32x32 & 16x16 versions), created a Xojo image using these png files. I added a shared member to enable/disable DiamondHiddenWindows and updated the code to apply this as a menuItem icon when applicable. NOT a perfect solution as the icon is not in the same position as the check-mark would have been. However, I deem it good enough for now. Certainly until after cross-platform testing.

Implementing In Your Program

The LDS_DesktopWIndowMenuItem implements the LDS_DarkModeResponsive interface (see  Who's Afraid of the Dark? - Making Dark Mode Work) to keep the 'window is minimized' diamond icon in sync with the appearance.

Example code:

Configure menu:

Configure the menu to use ALL available special menus.

call LDS_DesktopWindowMenuItem.configureWindowMenu(LDS_DesktopWindowMenuItem.specialMenuConfiguration.All)

ConfigureEnable or disable 'diamond' marks for hidden windows:

LDS_DesktopWindowMenuItem.diamondHiddenWindow=true

Add sub-menu to a window’s menu item:

This example will add a sub-menu with one entry to the Window menu item in the Window menu that corresponds to the currently visible 'topWindow' of LDS_DesktopWIndowListable class.

var topWinListable as DesktopWindow = LDS_DesktopWindowListable.topWindow
if topWinListable <> nil then
  var theWinMenuItem as DesktopMenuItem = LDS_DesktopWindowMenuItem.getMenuItemforWindow(twl)
  if (theWinMenuItem <>nil) then
    var newMenuItem as DesktopMenuItem = new winMenuItemSpecialOp(“Special Operation on Window")
    theWinMenuItem.AddMenu(newMenuItem)
  end
end
Return True

Create a new sub-class of desktopMenuItem (in this example named 'winMenuItemSpecialOP' and implement the MenuItemSelected event handler (a trivial example is shown below)

 system.DebugLog("winMenuItemSpecialOP menuItem selected")

Update FileClose menu handler to properly handle LDS_DesktopWIndowListable

This code will close the top window, or the latest window which was visible.

// Get the window we want to close and remove it
if(app.WindowCount>0) then
  if(app.windowAt(0) isa LDS_DesktopWindowListable) then
    LDS_DesktopWindowListable.WindowList(0).close
  else
    app.WindowAt(0).close
  end
end
 

Why not use some declares or third-party plug-ins?

I normally avoid using declares if possible. The main reason is that they are OS specific and make your code more brittle.  When these solutions eventually break due to OS changes, I am stuck trying to dig into it to find the problem. If I can stick to pure Xojo solutions, then I have the whole Xojo team protecting me from ever having to deal with it. I have no doubt that MBS (Monkey Bread Software) or one of the other great commercial tool sets out there have the solutions to these issues. However, for many of us, these tools may be too large an expense, not strictly money, but also of time. You don't pay hundreds of dollars for a tool and then not invest properly in study and testing to learn to best use it. That expense can be simply too high when your Xojo apps are not a main part of your livelihood or daily work.