1. Trays
  2. How to Make your App "Minimize to Tray" on Quit

Trays

How to Make your App "Minimize to Tray" on Quit

Learn how to minimize an application to the tray when a user closes it

Introduction

Minimizing a windowed application to the tray menu is a common use-case for apps that require an always-on presence. This has the effect of keeping your app in the tray menu, but removing it from the dock whenever a user closes it. In this walkthrough, we'll learn how to add this behaviour to an existing application.

ToDesktop Builder Configuration

Navigate to the "Application" menu in ToDesktop Builder. Once there, check the "Minimize App on Quit" checkbox:

App options for configuring minmize app

For this behaviour to activate, we'll need to create a Tray that has one of its click handlers set to "Toggle Menu". To do so, navigate to "Trays" and create your first tray. Once created, set the left-click to "Toggle Menu".

Select dropdown with 'Toggle Menu' selected

Pressing the "run" button should open the application like before:

Context menu that displays dynamically-generated menu items

However, when you exit the app, you should see the app icon disappear from the dock:

Context menu that displays dynamically-generated menu items

To quit an app completely, the user instead needs to click on the tray icon and select "Quit Completely".

Updating the Tray Menu Programmatically

If you're comfortable extending ToDesktop Builder apps with custom code, then you can programmatically manage your newly-created Tray with our @todesktop/client-core package. For example, we may want to display different menu options depending on whether the app is open or closed:

Context menu that displays dynamically-generated menu items
Context menu that displays dynamically-generated menu items

To do so, we'll need to do the following:

  • Retrieve a reference to the tray that we created in ToDesktop Builder.
  • Create a function for attaching a tray menu.
  • Attach the tray menu in response to window events.
  • Initialize the tray menu depending on window visibility.

You can skip to the full implementation at the bottom of the page if you want to see the final code.

Code Walkthrough

To start, we’ll sketch out the body of the function and get our imports out of the way:

        import { app, nativeWindow, tray, menu, object } from '@todesktop/client-core';

async function updateMenuOnWindowChanges() {
  // retrieve tray reference
  // create function for attaching trays
  // attach tray in response to events
  // initialize the tray menu
}

      

Next, we'll use the object API to retrieve the tray that we created in ToDesktop Builder. Copy your tray ID and use it as follows:

Copyable input that is pre-filled with the tray identifier
        async function updateMenuOnWindowChanges() {
  // retrieve tray reference
  const trayRef = await object.retrieve({ id: '3FQAQO9vS3qFPp5Xgr353' });

  // create function for attaching trays
  // attach tray in response to events
  // initialize the tray menu
}

      

We'll now create a utility function for attaching a tray menu. The function will take in a menuItem as an input, and will construct a menu that can be attached to a tray. Importantly, we'll also add a click handler that allows the user to quit the app completely.

        async function updateMenuOnWindowChanges() {
  // retrieve tray reference (collapsed)

  // create function for attaching trays
  const attachTrayMenu = async (menuItem) => {
    const menuRef = await menu.buildFromTemplate({
      template: [
        menuItem,
        { type: 'separator' },
        { label: 'Quit TodoMVC', click: () => app.quit() }
      ]
    });

    await tray.setContextMenu({ ref: trayRef, menu: menuRef });
  };

  // attach tray in response to events
  // initialize the tray menu
}

      

Next, we'll listen to show and hide events on the main window. We'll want to react as follows:

  • show: When this is triggered, we'll add a menu option for hiding the window.
  • hide: When this is triggered, we'll add a menu option for showing the window.
        async function updateMenuOnWindowChanges() {
  // retrieve tray reference (collapsed)

  // create function for attaching trays (collapsed)

  // attach tray in response to events
  const addShowMenu = async () => {
    await attachTrayMenu({
      label: 'Hide TodoMVC',
      click: () => nativeWindow.hide()
    });
  };

  const addHideMenu = async () => {
    await attachTrayMenu({
      label: 'Open TodoMVC',
      click: () => nativeWindow.show()
    });
  };

  nativeWindow.on('show', addShowMenu);
  nativeWindow.on('hide', addHideMenu);

  // initialize the tray menu
}

      

Finally, we'll initialize the appropriate menu depending on the visibility of the window.

        async function updateMenuOnWindowChanges() {
  // retrieve tray reference (collapsed)

  // create function for attaching trays (collapsed)

  // attach tray in response to events (collapsed)

  // initialize the tray menu
  if (await nativeWindow.isVisible()) {
    addShowMenu();
  } else {
    addHideMenu();
  }
}

      

With all of the hard work done, we'll simply need to invoke our function:

        updateMenuOnWindowChanges();

      

And that's it! Our tray menu will now update dynamically depending on the visibility status of the window.

Context menu that displays dynamically-generated menu items
Context menu that displays dynamically-generated menu items

Full Implementation

        import { app, nativeWindow, tray, menu, object } from '@todesktop/client-core';

async function updateMenuOnWindowChanges() {
  // retrieve tray reference
  const trayRef = await object.retrieve({ id: '3FQAQO9vS3qFPp5Xgr353' });

  // create function for attaching trays
  const attachTrayMenu = async (menuItem) => {
    const menuRef = await menu.buildFromTemplate({
      template: [
        menuItem,
        { type: 'separator' },
        { label: 'Quit TodoMVC', click: () => app.quit() }
      ]
    });

    await tray.setContextMenu({ ref: trayRef, menu: menuRef });
  };

  // attach tray in response to events
  const addShowMenu = async () => {
    await attachTrayMenu({
      label: 'Hide TodoMVC',
      click: () => nativeWindow.hide()
    });
  };

  const addHideMenu = async () => {
    await attachTrayMenu({
      label: 'Open TodoMVC',
      click: () => nativeWindow.show()
    });
  };

  nativeWindow.on('show', addShowMenu);
  nativeWindow.on('hide', addHideMenu);

  // initialize the tray menu
  if (await nativeWindow.isVisible()) {
    addShowMenu();
  } else {
    addHideMenu();
  }
}

updateMenuOnWindowChanges();