Skip to content

Tray Service

Sam Johnson edited this page Jan 3, 2021 · 5 revisions

The tray service provides notification area icon support.

There are a few objects involved with using the tray service:

  • NotificationArea provides the PinnedIcons and UnpinnedIcons collections, as well as service management and lifecycle methods.
  • TrayService provides the Windows hook functionality allowing applications to display notification icons using Shell_NotifyIcon.
  • ExplorerTrayService provides icons from the Windows Explorer notification area as a seed.
  • NotifyIcon objects represent each individual notification icon.

Lifecycle

Using ShellManager

If you are using the ShellManager to instantiate ManagedShell, the lifecycle of the tray service is managed for you. However, you can customize this behavior by passing a ShellConfig object to the ShellManager constructor to modify settings:

// Initialize the default configuration.
ShellConfig config = ShellManager.DefaultShellConfig;

// Customize tray service options.
config.EnableTrayService = true; // controls whether the tray objects are instantiated in ShellManager, true by default
config.AutoStartTrayService = false; // controls whether the tray service is started as part of ShellManager instantiation, true by default
config.PinnedNotifyIcons = new[] { "GUID or PathToExe:UID" }; // sets the initial NotifyIcons that should be included in the PinnedIcons collection, by default Action Center (prior to Windows 10 only), Power, Network, and Volume.

// Initialize the shell manager.
ShellManager _shellManager = new ShellManager(config);

// Initialize the tray service, since we disabled auto-start above.
_shellManager.NotificationArea.Initialize();

Custom

The tray service can be used without management via ShellManager if desired.

using ManagedShell.WindowsTray;

...

// Instantiate service.
TrayService trayService = new TrayService();
ExplorerTrayService explorerTrayService = new ExplorerTrayService();

// Initialize collections with the default set of pinned icons.
NotificationArea notificationArea = new NotificationArea(NotificationArea.DEFAULT_PINNED, trayService, explorerTrayService);

// Start the service.
notificationArea.Initialize();

// Access the notification area icons.
MyTrayPinnedItemsControl.ItemsSource = notificationArea.PinnedIcons;
MyTrayUnpinnedItemsControl.ItemsSource = notificationArea.UnpinnedIcons;

// Dispose of the service.
notificationArea.Dispose();

Pinned Notification Area Icons

Pinned icons allow certain notification icons to be displayed separately in your UI. By default, if you use ShellManager, the Action Center (prior to Windows 10 only), Power, Network, and Volume icons will be included in this collection (the default pinned icons are defined in NotificationArea.DEFAULT_PINNED).

Management

To allow for your application to manage pinned icons, several methods are available on each NotifyIcon object in the PinnedIcons and UnpinnedIcons collections:

private void DoStuffToNotifyIcon(NotifyIcon icon)
{
    // Mark icon as pinned
    icon.Pin();
    
    // Mark icon as pinned, at a specific index in the collection
    icon.Pin(3);
    
    // Mark icon as unpinned
    icon.Unpin();
}

The NotificationArea object also can also be used to update pinned icons post-instantiation:

// Replace the pinned icons string array with a new one.
notificationArea.SetPinnedIcons(myPinnedIcons);

Tray Host Size

Many notification area icons request for the size and placement of the notification area control, usually for the purpose of positioning a "flyout" interface style. Windows Explorer uses the placement of the taskbar for this purpose, but the control to return placement data from will vary by application.

To set the tray host size, create a TrayHostSizeData struct and pass it to the NotificationArea.SetTrayHostSizeData method:

private void SetTrayHostSizeData()
{
    // Create struct with size and position data about the tray host control
    TrayHostSizeData hostSize = new TrayHostSizeData
    {
        edge = NativeMethods.ABEdge.ABE_BOTTOM,
        rc = new NativeMethods.Rect
        {
            Top = 0,
            Left = 0,
            Bottom = 0,
            Right = 0
        }
    };
    
    // Set the tray host size data.
    notificationArea.SetTrayHostSizeData(hostSize);
}

If you are using the AppBarWindow class, you can override the AfterAppBarPos and SetPosition methods to automatically set this whenever the window's position or size change:

private void SetTrayHostSizeData()
{
    // Create struct with size and position data about the tray host control
    TrayHostSizeData hostSize = new TrayHostSizeData
    {
        edge = AppBarEdge,
        rc = new NativeMethods.Rect
        {
            Top = (int)(Top * DpiScale),
            Left = (int)(Left * DpiScale),
            Bottom = (int)((Top + Height) * DpiScale),
            Right = (int)((Left + Width) * DpiScale)
        }
    };
    
    // Set the tray host size data.
    notificationArea.SetTrayHostSizeData(hostSize);
}

public override void AfterAppBarPos(bool isSameCoords, NativeMethods.Rect rect)
{
    base.AfterAppBarPos(isSameCoords, rect);
    SetTrayHostSizeData();
}

public override void SetPosition()
{
    base.SetPosition();
    SetTrayHostSizeData();
}

Notification Area Icon Interaction

User interaction with notification area icons can be achieved by calling the appropriate methods on the NotifyIcon object from your notification area icon control.

Several mouse events should call corresponding methods on the NotifyIcon object, passing in the cursor position:

private void NotifyIcon_OnMouseUp(object sender, MouseButtonEventArgs e)
{
    e.Handled = true;
    notifyIcon.IconMouseClick(e.ChangedButton, MouseHelper.GetCursorPositionParam(), System.Windows.Forms.SystemInformation.DoubleClickTime);
}

private void NotifyIcon_OnMouseLeave(object sender, MouseEventArgs e)
{
    e.Handled = true;
    notifyIcon.IconMouseLeave(MouseHelper.GetCursorPositionParam());
}

private void NotifyIcon_OnMouseMove(object sender, MouseEventArgs e)
{
    e.Handled = true;
    notifyIcon.IconMouseMove(MouseHelper.GetCursorPositionParam());
}

The MouseEnter event should additionally set the NotifyIcon.Placement property based on the notification area icon control's placement. Several notification area icons request the placement of the notification area icon control, and setting this on the MouseEnter event allows this information to be as accurate as possible once the icon is interacted with.

// In this example, mouse events are being sent from a WPF Border, which contains an Image.
// This allows for any margin around the icon image to respond to mouse events.
// As such, we make the assumption that the sender is a Decorator.
private void NotifyIcon_OnMouseEnter(object sender, MouseEventArgs e)
{
    e.Handled = true;
    
    // get icon position for Shell_NotifyIconGetRect
    Decorator sendingDecorator = sender as Decorator;
    Point location = sendingDecorator.PointToScreen(new Point(0, 0));
    double dpiScale = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice.M11;
    
    // set icon position for Shell_NotifyIconGetRect
    notifyIcon.Placement = new NativeMethods.Rect
    {
        Top = (int)location.Y,
        Left = (int)location.X,
        Bottom = (int)(sendingDecorator.ActualHeight * dpiScale),
        Right = (int)(sendingDecorator.ActualWidth * dpiScale)
    };
    
    notifyIcon.IconMouseEnter(MouseHelper.GetCursorPositionParam());
}