Как интегрировать 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(...)
ничего не делает, нет ошибок, нет ничего.
Краткое изложение проблем:
- Binding Message.Attach не работает, хотя Caliburn выводит сообщения регистрации для этого, так как он работает.
-
Вызов 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;
}
}
КАК ПОЛЬЗОВАТЬСЯ:
- Создайте представление, которое наследуется от 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">
- Создайте модель вида для вида:
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 :(
}
}
Инстанцируйте это через ITrayIconManager в любом месте. Например, в
OnActivat
метод вашей ShellViewModel:protected override void OnActivate() { trayIcon = trayIconManager.GetOrCreateFor<TrayIconViewModel>(); }
Используйте когда захотите. Например, в моем 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
метод.