WPF MVVM:如何关闭窗口

Bob*_*Bob 67 .net c# wpf xaml mvvm

点击后,我Button关闭了我的窗口:

<Button x:Name="buttonOk"  IsCancel="True">Ok</Button>
Run Code Online (Sandbox Code Playgroud)

这很好,直到我添加CommandButtonie

<Button x:Name="buttonOk" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>
Run Code Online (Sandbox Code Playgroud)

现在它不会因为我正在处理它而关闭Command.我可以通过EventHandler输入和调用this.Close()ie 来解决这个问题

<Button x:Name="buttonOk" 
        Click="closeWindow" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>
Run Code Online (Sandbox Code Playgroud)

但现在我的代码背后有代码,即方法SaveCommand.我使用的是MVVM模式,SaveCommand是我代码中唯一的代码.

我怎么能以不同的方式做到这一点,以免背后使用代码?

Jon*_*hay 54

我刚刚完成了关于这个主题的博客文章.简而言之,Action使用getset访问器向ViewModel 添加属性.然后ActionView构造函数中定义.最后,在应该关闭窗口的bound命令中调用您的操作.

在ViewModel中:

public Action CloseAction  { get; set;}
Run Code Online (Sandbox Code Playgroud)

并在View构造函数中:

private View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel();
    this.DataContext = vm;
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(this.Close);
}
Run Code Online (Sandbox Code Playgroud)

最后,在应该关闭窗口的任何绑定命令中,我们可以简单地调用

CloseAction(); // Calls Close() method of the View
Run Code Online (Sandbox Code Playgroud)

这对我有用,看起来像是一个相当优雅的解决方案,并为我节省了大量编码.

  • 请原谅我的无知,但这怎么不违反将View和ViewModel分离的原则?如果要在View中实例化ViewModel,也可以不使用MVVM.我认为最佳实践是单独实例化View和ViewModel,并将DataContext设置为视图本身之外的View. (10认同)
  • 我意识到这已经老了,但是我认为这个方法不会破坏MVVM,除非有一个我不知道的严格定义.最终,MVVM要求VM不知道视图,但视图必须知道VM.如果要替换视图,则不会以任何方式破坏VM.将会有一个未经实例化的Action,但我认为这并不是MVVM规则被破坏的声明.搜索"WPF DataContext Instantiation"将在许多文章中提出这种方法. (9认同)
  • 你可以做构造函数注入而不是属性注入来摆脱空检查:http://programmers.stackexchange.com/questions/177649/what-is-constructor-injection`his.DataContext = new ViewModel(this.Close) ;`然后在ViewModel的构造函数中,您将近似分配给CloseAction.这也有使CloseAction get-only的优点. (5认同)
  • 通过使 Action 成为静态属性来解决它。啊啊啊! (2认同)

小智 15

有人评论说,我发布的代码不是MVVM友好的,第二个解决方案怎么样?

1,不是MVVM解决方案(我不会删除这个作为参考)

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
Run Code Online (Sandbox Code Playgroud)

视图模型:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    // Your Code
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }
Run Code Online (Sandbox Code Playgroud)

2,可能更好的解决方案: 使用附加行为

XAML

<Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" />
Run Code Online (Sandbox Code Playgroud)

查看模型

public ICommand OkCommand
{
    get { return _okCommand; }
}
Run Code Online (Sandbox Code Playgroud)

行为类与此类似:

public static class CloseOnClickBehaviour
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached(
            "IsEnabled",
            typeof(bool),
            typeof(CloseOnClickBehaviour),
            new PropertyMetadata(false, OnIsEnabledPropertyChanged)
        );

    public static bool GetIsEnabled(DependencyObject obj)
    {
        var val = obj.GetValue(IsEnabledProperty);
        return (bool)val;
    }

    public static void SetIsEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(IsEnabledProperty, value);
    }

    static void OnIsEnabledPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
    {
        var button = dpo as Button;
        if (button == null)
            return;

        var oldValue = (bool)args.OldValue;
        var newValue = (bool)args.NewValue;

        if (!oldValue && newValue)
        {
            button.Click += OnClick;
        }
        else if (oldValue && !newValue)
        {
            button.PreviewMouseLeftButtonDown -= OnClick;
        }
    }

    static void OnClick(object sender, RoutedEventArgs e)
    {
        var button = sender as Button;
        if (button == null)
            return;

        var win = Window.GetWindow(button);
        if (win == null)
            return;

        win.Close();
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 你永远不会和我重复,永远不要把Window和ViewModel结合起来.现在写句子100个实例:) (30认同)
  • @Barahovski:Window是一个WPF对象.viewmodel不应该依赖于WPF或任何繁重的框架.单元测试(没有UI)如何获得一个Window实例来测试它? (7认同)
  • +1恕我直言这是最好的解决方案:它做的事情,它是最短的,不需要复杂的基础设施,解决MVVM方式的问题.@SoMoS--这里没有耦合.完全没有.VM不知道View存在; 命令获取Window作为参数,因为它需要知道要关闭的内容. (4认同)
  • +1 @SoMoS我同意Ilia这正是解耦解决方案,我不会将保存和关闭窗口逻辑结合在一起,但这是另一回事 (2认同)

小智 12

我个人会使用一种行为来做这种事情:

public class WindowCloseBehaviour : Behavior<Window>
{
    public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register(
        "Command",
        typeof(ICommand),
        typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CommandParameterProperty =
      DependencyProperty.Register(
        "CommandParameter",
        typeof(object),
        typeof(WindowCloseBehaviour));

    public static readonly DependencyProperty CloseButtonProperty =
      DependencyProperty.Register(
        "CloseButton",
        typeof(Button),
        typeof(WindowCloseBehaviour),
        new FrameworkPropertyMetadata(null, OnButtonChanged));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    public Button CloseButton
    {
        get { return (Button)GetValue(CloseButtonProperty); }
        set { SetValue(CloseButtonProperty, value); }
    }

    private static void OnButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = (Window)((WindowCloseBehaviour)d).AssociatedObject;
        ((Button) e.NewValue).Click +=
            (s, e1) =>
            {
                var command = ((WindowCloseBehaviour)d).Command;
                var commandParameter = ((WindowCloseBehaviour)d).CommandParameter;
                if (command != null)
                {
                    command.Execute(commandParameter);                                                      
                }
                window.Close();
            };
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后,您可以将此附加到您的工作中WindowButton执行以下操作:

<Window x:Class="WpfApplication6.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApplication6"
        Title="Window1" Height="300" Width="300">
    <i:Interaction.Behaviors>
        <local:WindowCloseBehaviour CloseButton="{Binding ElementName=closeButton}"/>
    </i:Interaction.Behaviors>
    <Grid>
        <Button Name="closeButton">Close</Button>
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

我已添加CommandCommandParameter在此处,因此您可以在Window关闭之前运行命令.


RAJ*_*RAJ 12

非常干净和MVVM的方式是使用InteractionTriggerCallMethodAction定义Microsoft.Interactivity.Core

您需要添加两个名称空间,如下所示

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Run Code Online (Sandbox Code Playgroud)

和程序集System.Windows.InteractivityMicrosoft.Expression.Interactions然后下面的xaml代码将工作.

<Button Content="Save" Command="{Binding SaveCommand}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
      <ei:CallMethodAction MethodName="Close"
                           TargetObject="{Binding RelativeSource={RelativeSource
                                                  Mode=FindAncestor,
                                                  AncestorType=Window}}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Button>
Run Code Online (Sandbox Code Playgroud)

您不需要任何代码或其他任何代码,也可以调用任何其他方法Window.

  • Rajnikant 的代码在 VS 2019 中的工作时间更长,因为 MS 将 WPF 行为开源并移至 Microsoft.Xaml.Behaviors.Wpf NuGet 包。信息来源是要发布的评论:https://developercommunity.visualstudio.com/content/problem/198075/microsoftexpressinteractions-is-missing-from-vi.html。重构代码的详细步骤位于:https://devblogs.microsoft.com/dotnet/open-commerce-xaml-behaviors-for-wpf/ (2认同)

Ily*_*gin 7

对于小型应用程序,我使用自己的应用程序控制器来显示,关闭和处理Windows和DataContexts.它是应用程序UI的中心点.

它是这样的:

//It is singleton, I will just post 2 methods and their invocations
public void ShowNewWindow(Window window, object dataContext = null, bool dialog = true)
{
    window.DataContext = dataContext;
    addToWindowRegistry(dataContext, window);

    if (dialog)
        window.ShowDialog();
    else
        window.Show();

}

public void CloseWindow(object dataContextSender)
{
    var correspondingWindows = windowRegistry.Where(c => c.DataContext.Equals(dataContextSender)).ToList();
    foreach (var pair in correspondingWindows)
    {
        pair.Window.Close();              
    }
}
Run Code Online (Sandbox Code Playgroud)

以及来自ViewModels的调用:

// Show new Window with DataContext
ApplicationController.Instance.ShowNewWindow(
                new ClientCardsWindow(),
                new ClientCardsVM(),
                false);

// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);
Run Code Online (Sandbox Code Playgroud)

当然,您可以在我的解决方案中找到一些限制.再说一次:我将它用于小型项目,这就足够了.如果您有兴趣,我可以在这里或其他地方发布完整代码/


lar*_*moa 5

我试图用一些通用的MVVM方式来解决这个问题,但我总是发现我最终得到了不必要的复杂逻辑.为了实现密切的行为,我已经从没有代码的规则中做出了例外,并且仅使用代码中的良好ol'事件:

XAML:

<Button Content="Close" Click="OnCloseClicked" />
Run Code Online (Sandbox Code Playgroud)

代码背后:

private void OnCloseClicked(object sender, EventArgs e)
{
    Visibility = Visibility.Collapsed;
}
Run Code Online (Sandbox Code Playgroud)

虽然我希望使用命令/ MVVM可以更好地支持这一点,但我认为没有比使用事件更简单,更清晰的解决方案.


Sco*_*rod 5

我使用Publish Subscribe模式来处理复杂的类依赖:

视图模型:

    public class ViewModel : ViewModelBase
    {
        public ViewModel()
        {
            CloseComand = new DelegateCommand((obj) =>
                {
                    MessageBus.Instance.Publish(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, null);
                });
        }
}
Run Code Online (Sandbox Code Playgroud)

窗口:

public partial class SomeWindow : Window
{
    Subscription _subscription = new Subscription();

    public SomeWindow()
    {
        InitializeComponent();

        _subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
            {
                this.Close();
            });
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以利用Bizmonger.Patterns来获取MessageBus.

MessageBus

public class MessageBus
{
    #region Singleton
    static MessageBus _messageBus = null;
    private MessageBus() { }

    public static MessageBus Instance
    {
        get
        {
            if (_messageBus == null)
            {
                _messageBus = new MessageBus();
            }

            return _messageBus;
        }
    }
    #endregion

    #region Members
    List<Observer> _observers = new List<Observer>();
    List<Observer> _oneTimeObservers = new List<Observer>();
    List<Observer> _waitingSubscribers = new List<Observer>();
    List<Observer> _waitingUnsubscribers = new List<Observer>();

    int _publishingCount = 0;
    #endregion

    public void Subscribe(string message, Action<object> response)
    {
        Subscribe(message, response, _observers);
    }

    public void SubscribeFirstPublication(string message, Action<object> response)
    {
        Subscribe(message, response, _oneTimeObservers);
    }

    public int Unsubscribe(string message, Action<object> response)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Respond == response).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Respond == response));
        observers.AddRange(_oneTimeObservers.Where(o => o.Respond == response));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public int Unsubscribe(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription).ToList());
        observers.AddRange(_waitingSubscribers.Where(o => o.Subscription == subscription));
        observers.AddRange(_oneTimeObservers.Where(o => o.Subscription == subscription));

        if (_publishingCount == 0)
        {
            observers.ForEach(o => _observers.Remove(o));
        }

        else
        {
            _waitingUnsubscribers.AddRange(observers);
        }

        return observers.Count;
    }

    public void Publish(string message, object payload)
    {
        _publishingCount++;

        Publish(_observers, message, payload);
        Publish(_oneTimeObservers, message, payload);
        Publish(_waitingSubscribers, message, payload);

        _oneTimeObservers.RemoveAll(o => o.Subscription == message);
        _waitingUnsubscribers.Clear();

        _publishingCount--;
    }

    private void Publish(List<Observer> observers, string message, object payload)
    {
        Debug.Assert(_publishingCount >= 0);

        var subscribers = observers.Where(o => o.Subscription.ToLower() == message.ToLower());

        foreach (var subscriber in subscribers)
        {
            subscriber.Respond(payload);
        }
    }

    public IEnumerable<Observer> GetObservers(string subscription)
    {
        var observers = new List<Observer>(_observers.Where(o => o.Subscription == subscription));
        return observers;
    }

    public void Clear()
    {
        _observers.Clear();
        _oneTimeObservers.Clear();
    }

    #region Helpers
    private void Subscribe(string message, Action<object> response, List<Observer> observers)
    {
        Debug.Assert(_publishingCount >= 0);

        var observer = new Observer() { Subscription = message, Respond = response };

        if (_publishingCount == 0)
        {
            observers.Add(observer);
        }
        else
        {
            _waitingSubscribers.Add(observer);
        }
    }
    #endregion
}
Run Code Online (Sandbox Code Playgroud)

}

订阅

public class Subscription
{
    #region Members
    List<Observer> _observerList = new List<Observer>();
    #endregion

    public void Unsubscribe(string subscription)
    {
        var observers = _observerList.Where(o => o.Subscription == subscription);

        foreach (var observer in observers)
        {
            MessageBus.Instance.Unsubscribe(observer.Subscription, observer.Respond);
        }

        _observerList.Where(o => o.Subscription == subscription).ToList().ForEach(o => _observerList.Remove(o));
    }

    public void Subscribe(string subscription, Action<object> response)
    {
        MessageBus.Instance.Subscribe(subscription, response);
        _observerList.Add(new Observer() { Subscription = subscription, Respond = response });
    }

    public void SubscribeFirstPublication(string subscription, Action<object> response)
    {
        MessageBus.Instance.SubscribeFirstPublication(subscription, response);
    }
}
Run Code Online (Sandbox Code Playgroud)