我有一个基本上运行计时器的窗口。当计时器达到 0 时,我想将窗口放在前面,以便它可见而不是隐藏在其他应用程序后面。
据我所知,我只需调用 window.activate() 即可完成此操作,但使用 mvvm 我的视图模型没有对窗口的引用。
“纯粹”的 MVVM 解决方案是使用行为。下面是Window
带有Activated
属性的 a 的行为。将该属性设置为 true 将激活窗口(如果它被最小化则恢复它):
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
该行为需要引用System.Windows.Interactivity.dll
. 幸运的是,现在可以在Blend.Interactivity.Wpf包中的 NuGet 上使用它。
该行为附加到 XAML 中的窗口,如下所示:
<Window ...>
<i:Interaction.Behaviors>
<Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
视图模型应该公开一个布尔Activated
属性。将此属性设置为 true 将激活窗口(除非它已被激活)。作为额外的奖励,它还将恢复一个最小化的窗口。
您可以通过几种方式来解决它 - 添加对窗口的引用可能会起作用,因为 viewmodel 不与视图耦合而是与之相关,但我不太喜欢这种方法,因为它几乎可以耦合您的视图到您的视图模型 - 这并不是 MVVM 的真正重点
更好的方法可能是让您的视图模型引发视图可以处理的事件或命令。通过这种方式,视图可以决定与命令/事件相关联的 UI 操作
例如简单地
class SomeView
{
void HandleSomeCommandOrEvent()
{
this.Activate();
}
}
当然,你如何连接这取决于你,但我可能会尝试让路由命令发生
编辑:你不能真正“绑定”一个简单的事件,因为它是从视图模型中调用的。
一个简单的基于事件的示例只是将事件添加到视图模型并直接处理它......例如想象以下带有 ViewModel 属性的 MainWindow
public partial class MainWindow : Window
{
MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
ViewModel.ShowMessage += ViewModel_ShowMessage;
this.DataContext = ViewModel;
}
void ViewModel_ShowMessage(object sender, ShowMessageEventArgs e)
{
MessageBox.Show(e.Message, "Some caption", MessageBoxButton.OK);
}
}
然后 ViewModel 可以触发事件:
// The view model
public class MainWindowViewModel
{
// The button click command
public RelayCommand ButtonClickCommand { get; set; }
// The event to fire
public event EventHandler<ShowMessageEventArgs> ShowMessage;
public MainWindowViewModel()
{
ButtonClickCommand = new RelayCommand(ButtonClicked);
}
void ButtonClicked(object param)
{
// This button is wired up in the view as normal and fires the event
OnShowMessage("You clicked the button");
}
// Fire the event - it's up to the view to decide how to implement this event and show a message
void OnShowMessage(string message)
{
if (ShowMessage != null) ShowMessage(this, new ShowMessageEventArgs(message));
}
}
public class ShowMessageEventArgs : EventArgs
{
public string Message { get; private set; }
public ShowMessageEventArgs(string message)
{
Message = message;
}
}
XAML 将是:
<Button Command="{Binding ButtonClickCommand}">Click me!</Button>
因此按钮调用命令,该命令反过来触发视图(MainWindow)处理的事件并显示消息框。通过这种方式,视图/UI 根据引发的事件类型决定操作过程。当然可能是你的计时器触发了这个事件
你总是可以走更复杂的路线,比如这个问题的一些答案......
但老实说,这取决于你是否真的需要它 - 一个简单的事件效果很好 - 有些人为了优雅而将事情过度复杂化,但却损害了简单性和生产力!
我会这样:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestActivateWindow : Window
{
public TestActivateWindow() {
InitializeComponent();
Messenger.Default.Register<ActivateWindowMsg>(this, (msg) => Activate());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _activateChildWindowCommand;
public ICommand ActivateChildWindowCommand {
get {
return _activateChildWindowCommand?? (_activateChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new ActivateWindowMsg());
}));
}
}
}
public class ActivateWindowMsg
{
}