如何在WPF和MVVM中将窗口置于前台

8

我有一个窗口,基本上运行一个计时器。当计时器归零时,我希望将窗口置于最前面,以便它可见,而不是隐藏在其他应用程序后面。

据我所知,我只需调用window.activate()就可以实现这一点,但是使用mvvm时,我的视图模型没有对窗口的引用。

3个回答

21

一个“纯粹”的MVVM解决方案是使用行为(behavior)。下面是一个针对WindowActivated属性的行为。将该属性设置为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。幸运的是,现在可以在NuGet上通过Blend.Interactivity.Wpf包获得此引用。

该行为在XAML中附加到窗口如下:

<Window ...>
  <i:Interaction.Behaviors>
    <Behaviors:ActivateBehavior Activated="{Binding Activated, Mode=TwoWay}"/>
  </i:Interaction.Behaviors>

视图模型应该公开一个布尔类型的Activated属性。将此属性设置为true将激活窗口(除非它已经处于激活状态)。作为额外奖励,它还将恢复最小化的窗口。

2
谢谢Martin。这才是真正的答案,而不是事件黑客。 - g.pickardou
当您将“Activated”属性设置为“false”时会发生什么? - romanoza
@RomanKo:除了属性变为false之外,你不应该将其设置为false。将属性设置为true以激活窗口,并获取属性值以确定窗口是否已激活。 - Martin Liversage

4
你可以用几种方法来实现 - 添加对窗口的引用可能会起作用,因为viewmodel不与视图耦合,但我并不喜欢这种方法,因为它实际上将你的视图与viewmodel耦合在一起,这不是MVVM的重点。
更好的方法可能是让viewmodel触发一个事件或命令,由视图处理。这样,视图可以决定与命令/事件相关的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基于引发的事件类型决定行动方案。当然,也可以是您的计时器触发了该事件。
您始终可以选择更复杂的路线,例如本问题中某些答案所述...
但是说实话,这取决于您是否真正需要它——简单的事件处理很有效——有些人为了追求优雅而过度复杂化事情,这对简单性和生产力都是不利的!

我正在学习 WPF 和绑定,因此这可能是一个简单的问题。如何将命令绑定到视图?我使用按钮来完成此操作,但我不知道如何在没有 WPF 控件的情况下完成此操作<Button Content="Add Task" Command="{Binding Path=AddTask}"/> 其中 AddTask 是一个 ICommand。 - Josh

0

我会选择这个方向:

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
{
}

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