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:
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:
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">
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 :(
}
}
Instanciate it through ITrayIconManager in any place. For example, in the OnActivat
method of your ShellViewModel:
protected override void OnActivate()
{
trayIcon = trayIconManager.GetOrCreateFor<TrayIconViewModel>();
}
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));
}