简体   繁体   中英

How to integrate WPF NotifyIcon with Caliburn.Micro

I was wondering how to integrate NotifyIcon with Caliburn.Micro.

I'm trying to integrate with Caliburn using low level Caliburn APIs. Here are the classes:

ITrayIconManager

public interface ITrayIconManager
{
    ITrayIcon GetOrCreateFor<T>();
}

ITrayIcon (wrapper around TaskbarIcon from WPF NotifyIcon )

 public interface ITrayIcon : IDisposable
{
    void ShowBalloonTip(string title, string message, BalloonIcon symbol);
    void Show();
    void Hide();
}

ISetTrayIconInstance

public interface ISetTrayIconInstance
{
    ITrayIcon Icon { set; }
}

TrayIconWrapper

public class TrayIconWrapper : ITrayIcon
{
    private readonly TaskbarIcon icon;

    public TrayIconWrapper(TaskbarIcon icon)
    {
        this.icon = icon;
    }

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        icon.Dispose();
        IsDisposed = true;
    }

    public void Show()
    {
        icon.Visibility = Visibility.Visible;
    }

    public void Hide()
    {
        icon.Visibility = Visibility.Collapsed;
    }

    public void ShowBalloonTip(string title, string message, BalloonIcon symbol)
    {
        icon.ShowBalloonTip(title, message, symbol);
    }
}

TrayIconManager

public class TrayIconManager : ITrayIconManager
{
    private readonly IDictionary<WeakReference, WeakReference> icons;

    public TrayIconManager()
    {
        icons = new Dictionary<WeakReference, WeakReference>();
    }

    public ITrayIcon GetOrCreateFor<T>()
    {
        if (!icons.Any(i => i.Key.IsAlive && typeof(T).IsAssignableFrom(i.Key.Target.GetType())))
            return Create<T>();

        var reference = icons.First(i => i.Key.IsAlive && typeof(T).IsAssignableFrom(i.Key.Target.GetType())).Value;
        if (!reference.IsAlive)
            return Create<T>();

        var wrapper = (TrayIconWrapper)reference.Target;
        if (wrapper.IsDisposed)
            return Create<T>();

        return wrapper;
    }

    private ITrayIcon Create<T>()
    {
        var rootModel = IoC.Get<T>();
        var view = ViewLocator.LocateForModel(rootModel, null, null);
        var icon = view is TaskbarIcon ? (TaskbarIcon)view : new TaskbarIcon();
        var wrapper = new TrayIconWrapper(icon);

        ViewModelBinder.Bind(rootModel, view, null);
        SetIconInstance(rootModel, wrapper);
        icons.Add(new WeakReference(rootModel), new WeakReference(wrapper));

        return wrapper;
    }

    private void SetIconInstance(object rootModel, ITrayIcon icon)
    {
        var instance = rootModel as ISetTrayIconInstance;
        if (instance != null)
            instance.Icon = icon;
    }
}

This is the code, now how do I use it? This code relies on Caliburn View - ViewModel binding , that is, I need to create a ViewModel for TasbarkIcon and a View (which must be inherited from TaskbarIcon control):

TrayIconViewModel

public class TrayIconViewModel : IMainTrayIcon, ISetTrayIconInstance
{
    public TrayIconViewModel()
    {

    }

    public ITrayIcon Icon { get; set; }

    public void ShowWindow()
    {
        Icon.Hide();
        System.Windows.Application.Current.MainWindow.Show(); //very very bad :(
    }

ITrayIcon is the wrapper for TaskbarIcon control. Now I can call methods on it from my ViewModel, which is great.

TrayIconView ( cal:Message:Attach doesn't work - the ShowWindow never gets hit)

<tb:TaskbarIcon x:Class="Communicator.Softphone.Views.TrayIconView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:tb="http://www.hardcodet.net/taskbar"
            xmlns:cal="http://www.caliburnproject.org"
            mc:Ignorable="d" 
            d:DesignHeight="300" d:DesignWidth="300"
            IconSource="/Communicator.ControlLibrary;component/Assets/phone_icon.ico"
            ToolTipText="Secretária do Futuro - Comunicador"
            Visibility="Collapsed"
            cal:Message.Attach="[Event TrayLeftMouseDown] = [Action ShowWindow()]">

On my ShellViewModel ( trayIcon is the wrapper around TaskbarIcon):

private ITrayIcon trayIcon;
protected override void OnActivate()
    {
        trayIcon = trayIconManager.GetOrCreateFor<IMainTrayIcon>();
        ActivateItem(containers.FirstOfType<IPhone>());
    }
public override void CanClose(Action<bool> callback)
    {
        trayIcon.Show();
        trayIcon.ShowBalloonTip("Comunicador", "Comunicador foi minimizado", BalloonIcon.Info);
        (GetView() as Window).Hide();
        callback(false);
    }

trayIcon.Show() is working, however trayIcon.ShowBallonTip(...) doesn't do anything, no errors, no nothing.

Issues summary :

  1. Binding Message.Attach is not working, although Caliburn output logging messages for this as it is working.
  2. Calling ShowBallonTip on the wrapper seems not to work, although it is calling the actual TaskbarIcon method. (it works without debugger attached)

You could use the Event Aggregator to do what you want.

Documentation: http://caliburnmicro.com/documentation/event-aggregator

Add a field to your TaskbarViewModel for the Event Aggregator and add a constructor to accomodate the injection:

public class TaskbarViewModel : PropertyChangedBase, ITaskbar {
    private readonly IEventAggregator _eventAggregator;

    public TaskbarViewModel(IEventAggregator eventAggregator) {
        _eventAggregator = eventAggregator;
    }

    public void Show() {
        IsVisible = true;
        _eventAggregator.PublishOnUIThread("Your balloontip message");
    }

    /// Rest of the implementation
}

Implement the IHandle interface where you can access the TaskBarIcon and call the ShowBalloonTip method.

The complete answer:

ITrayIcon.cs

Wraps TaskbarIcon control, so you could call methods on TaskbarIcon without having an actual reference to it in your view models.

public interface ITrayIcon : IDisposable
{
    void Show();
    void Hide();
    void ShowBalloonTip(string title, string message);
    void ShowBalloonTip(object rootModel, PopupAnimation animation, TimeSpan? timeout = null);
    void CloseBalloon();
}

ISetTrayIconInstance.cs

public interface ISetTrayIconInstance
{
    ITrayIcon Icon { set; }
}

ITrayIconManager.cs

Manages TasbarIcon instances. You can have as many TasbarIcon instances as you like.

public interface ITrayIconManager
{
    ITrayIcon GetOrCreateFor<T>();
}

TrayIconWrapper.cs

The implementation leverages from Caliburn ViewModelBinder. ShowBallonTip works alike IWindowManager.ShowWindow(object rootModel...) does. It instanciates the view through ViewLocator , binds your rootModel to it and then passes to TaskbarIcon.ShowCustomBallon(UIElement element... .

public class TrayIconWrapper : ITrayIcon
{
    private readonly TaskbarIcon icon;

    public TrayIconWrapper(TaskbarIcon icon)
    {
        this.icon = icon;
    }

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        icon.Dispose();
        IsDisposed = true;
    }

    public void Show()
    {
        icon.Visibility = Visibility.Visible;
    }

    public void Hide()
    {
        icon.Visibility = Visibility.Collapsed;
    }

    public void ShowBalloonTip(string title, string message)
    {
        icon.ShowBalloonTip(title, message, BalloonIcon.Info);
    }

    public void ShowBalloonTip(object rootModel, PopupAnimation animation, TimeSpan? timeout = null)
    {
        var view = ViewLocator.LocateForModel(rootModel, null, null);
        ViewModelBinder.Bind(rootModel, view, null);

        icon.ShowCustomBalloon(view, animation, timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : (int?)null);
    }

    public void CloseBalloon()
    {
        icon.CloseBalloon();
    }
}

TrayIconManager.cs

We need to keep track of the instanciated TaskbarIcon's. This class is the glue that binds all together. Need an instance of some TaskbarIcon? Ask to TrayIconManager, and it will create one (if it isn't already created and alive) and return it to you. The T generic type is the type of your view model that manages an instance of TaskbarIcon.

public class TrayIconManager : ITrayIconManager
{
    private readonly IDictionary<WeakReference, WeakReference> icons;

    public TrayIconManager()
    {
        icons = new Dictionary<WeakReference, WeakReference>();
    }

    public ITrayIcon GetOrCreateFor<T>()
    {
        if (!icons.Any(i => i.Key.IsAlive && typeof(T).IsAssignableFrom(i.Key.Target.GetType())))
            return Create<T>();

        var reference = icons.First(i => i.Key.IsAlive && typeof(T).IsAssignableFrom(i.Key.Target.GetType())).Value;
        if (!reference.IsAlive)
            return Create<T>();

        var wrapper = (TrayIconWrapper)reference.Target;
        if (wrapper.IsDisposed)
            return Create<T>();

        return wrapper;
    }

    private ITrayIcon Create<T>()
    {
        var rootModel = IoC.Get<T>();
        var view = ViewLocator.LocateForModel(rootModel, null, null);
        var icon = view is TaskbarIcon ? (TaskbarIcon)view : new TaskbarIcon();
        var wrapper = new TrayIconWrapper(icon);

        ViewModelBinder.Bind(rootModel, view, null);
        SetIconInstance(rootModel, wrapper);
        icons.Add(new WeakReference(rootModel), new WeakReference(wrapper));

        return wrapper;
    }

    private void SetIconInstance(object rootModel, ITrayIcon icon)
    {
        var instance = rootModel as ISetTrayIconInstance;
        if (instance != null)
            instance.Icon = icon;
    }
}

HOW TO USE :

  1. Create a View that inherits from TaskbarIcon:

TrayIconView.xaml

<tb:TaskbarIcon x:Class="Communicator.Softphone.Views.TrayIconView"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:tb="http://www.hardcodet.net/taskbar"
            xmlns:cal="http://www.caliburnproject.org"
            mc:Ignorable="d" 
            d:DesignHeight="300" d:DesignWidth="300"
            IconSource="/Communicator.ControlLibrary;component/Assets/phone_icon.ico"
            ToolTipText="Secretária do Futuro - Comunicador"
            cal:Message.Attach="[Event TrayLeftMouseDown] = [Action ShowWindow()]"
            TrayLeftMouseDown="TaskbarIcon_TrayLeftMouseDown">

  1. Create a view model for the view:

TrayIconViewModel.cs

public class TrayIconViewModel : ISetTrayIconInstance
{
    public TrayIconViewModel()
    {

    }

    public ITrayIcon Icon { get; set; }

    public void ShowWindow()
    {
        System.Windows.Application.Current.MainWindow.Show(); //very very bad :(
    }
}
  1. Instanciate it through ITrayIconManager in any place. For example, in the OnActivat method of your ShellViewModel :

     protected override void OnActivate() { trayIcon = trayIconManager.GetOrCreateFor<TrayIconViewModel>(); } 
  2. Use whenever you like. For example, in my ChatManager :

     public void NewMessage(IChatMessage message) { trayIcon = trayIconManager.GetOrCreateFor<TrayIconViewModel>(); var notification = new ChatNotificationViewModel(message); trayIcon.ShowBalloonTip(notification, PopupAnimation.Slide, TimeSpan.FromSeconds(5)); } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM