如何将WPF NotifyIcon与Caliburn.Micro集成

4

我想知道如何将NotifyIcon与Caliburn.Micro集成。

我正在尝试使用低级别的Caliburn API进行集成。以下是相关类:

ITrayIconManager

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

ITrayIcon 是一个包装器,它封装了WPF NotifyIcon中的TaskbarIcon

 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绑定,也就是说,我需要为TasbarkIcon创建一个ViewModel和一个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中调用它的方法,这非常棒。

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

关于我的ShellViewModeltrayIcon是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. 绑定Message.Attach无效,尽管Caliburn输出日志表明已经生效。
  2. wrapper上调用ShowBallonTip似乎不起作用,尽管它调用了实际的TaskbarIcon方法。(在未连接调试器的情况下可以正常工作)

1
我正在研究这个。请给我一分钟。 - Frank
抱歉...我已经编辑过了以说明我的情况 :) - JobaDiniz
抱歉,只是想问一下,你是否有System.Windows.Forms的参考资料,如果有的话,你可以直接在视图模型中初始化NotifyIcon。希望这可能会有所帮助。 - D_Learning
我有兴趣使用 wpf-notify-icon,它内置了许多适用于 WPF 的功能;而不是使用来自 System.Windows.Forms 的 NotifyIcon。 - JobaDiniz
你好,BrainCrumbz团队在这里。我看了你在CodeProject上的回复。我不确定我真正理解你的意思,我有点迷失在所有的包装等中。你看过我们的示例Github项目了吗,特别是第四个例子? - superjos
我试过了,那样做没用。那么ShowCustomBallon和ShowBallonTip方法怎么样?我需要在没有实际引用TaskbarIcon的情况下从我的视图模型中调用这些方法。我设法实现了它...唯一的问题是绑定操作。现在我能够这样做:trayIcon.ShowBalloonTip(new AnyViewModel(), PopupAnimation.Slide, TimeSpan.FromSeconds(5)); 这真的很好,因为Caliburn将负责实例化AnyViewModel的视图。 - JobaDiniz
2个回答

3
完整回答:
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

管理托盘图标实例。您可以拥有任意数量的托盘图标实例。

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

TrayIconWrapper.cs

这个实现利用了Caliburn ViewModelBinder。 ShowBallonTip 的工作方式类似于IWindowManager.ShowWindow(object rootModel...)。它通过ViewLocator实例化视图,将您的rootModel绑定到它上面,然后传递给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的实例吗?向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继承的View:

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. 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));
    }
    

3
你可以使用事件聚合器来实现你想要的功能。
文档:http://caliburnmicro.com/documentation/event-aggregatorTaskbarViewModel中添加一个字段来存储事件聚合器,并添加一个构造函数以适应注入。
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方法。


我的ViewModel都无法访问TaskBarIcon...它们应该访问吗? TaskBarIcon是一个控件,与视图相关...我可以发布一个事件,但谁会听呢?没有人...我想正确的实现方式类似于如何使用IWindowManager来管理窗口。对此的一些想法会很棒。 - JobaDiniz
_view_变量的类型为TrayIconView,但是_TrayIconView_继承自TaskbarIcon,所以我只需将其强制转换为_TaskbarIcon_,因为我不知道实际类型。 - JobaDiniz
感谢您的帮助...我的处理程序附加到TrayIconViewModel而不是ShellViewModel。当附加到TrayIconViewModel时,您的测试是否有效? - JobaDiniz
不是这样的,这就是为什么我尝试将其附加到“ShellViewModel”,并在“ShellView”中使用“TrayIconView”。从“ViewModelBinder”的调试日志中可以看出它无法解析“ShowWindow”。将此添加到您的引导程序:var baseGetLog = LogManager.GetLog;LogManager.GetLog = t => t == typeof (ViewModelBinder) ? new DebugLog(t) : baseGetLog(t); - Roel van Westerop
我得到了 [Caliburn.Micro.ViewModelBinder] INFO: Action Convention Not Applied: No actionable element for ShowWindow. - JobaDiniz
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接