Как интегрировать WPF NotifyIcon с Caliburn.Micro

Мне было интересно, как интегрировать NotifyIcon с Caliburn.Micro.

Я пытаюсь интегрироваться с Caliburn, используя низкоуровневые API Caliburn. Вот классы:

ITrayIconManager

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

ITrayIcon (обертка вокруг TaskbarIcon из 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;
    }
}

Это код, теперь, как мне его использовать? Этот код опирается на привязку Caliburn View - ViewModel, то есть мне нужно создать ViewModel для TasbarkIcon и View (который должен быть унаследован от элемента управления TaskbarIcon):

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 - это оболочка для элемента управления TaskbarIcon. Теперь я могу вызывать методы из моего ViewModel, и это здорово.

TrayIconView (cal: Message: Attach не работает - окно ShowWindow никогда не срабатывает)

<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()]">

На моей ShellViewModel (trayIcon - это оболочка вокруг 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() работает, однако trayIcon.ShowBallonTip(...) ничего не делает, нет ошибок, нет ничего.

Краткое изложение проблем:

  1. Binding Message.Attach не работает, хотя Caliburn выводит сообщения регистрации для этого, так как он работает.
  2. Вызов ShowBallonTip для оболочки, похоже, не работает, хотя он вызывает фактический метод TaskbarIcon. (работает без отладчика)

2 ответа

Решение

Полный ответ:

ITrayIcon.cs

Обертывает элемент управления TaskbarIcon, чтобы вы могли вызывать методы в TaskbarIcon, не имея фактической ссылки на него в моделях представления.

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

Управляет экземплярами TasbarIcon. Вы можете иметь столько экземпляров TasbarIcon, сколько захотите.

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

TrayIconWrapper.cs

Реализация использует Caliburn ViewModelBinder. ShowBallonTip работает одинаково IWindowManager.ShowWindow(object rootModel...) делает. Это создает представление через ViewLocator , привязывает вашу корневую модель к нему и затем переходит к 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

Нам нужно отслеживать экземпляры TaskbarIcon. Этот класс - клей, который связывает все вместе. Нужен экземпляр некоторого TaskbarIcon? Попросите TrayIconManager, и он создаст его (если он еще не создан и не существует) и вернет его вам. Универсальный тип T - это тип модели представления, которая управляет экземпляром 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;
    }
}

КАК ПОЛЬЗОВАТЬСЯ:

  1. Создайте представление, которое наследуется от 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. Создайте модель вида для вида:

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. Инстанцируйте это через ITrayIconManager в любом месте. Например, в OnActivat метод вашей ShellViewModel:

    protected override void OnActivate()
    {
        trayIcon = trayIconManager.GetOrCreateFor<TrayIconViewModel>();
    }
    
  2. Используйте когда захотите. Например, в моем ChatManager:

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

Вы можете использовать агрегатор событий, чтобы делать то, что вы хотите.

Документация: http://caliburnmicro.com/documentation/event-aggregator

Добавьте поле к вашему TaskbarViewModel для агрегатора событий и добавьте конструктор для размещения инъекции:

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
}

Реализовать IHandle интерфейс, где вы можете получить доступ к TaskBarIcon и позвонить ShowBalloonTip метод.

Другие вопросы по тегам