WPF MVVM:如何关闭窗口


78

我有一个Button它被点击时关闭我的窗口:

<Button x:Name="buttonOk"  IsCancel="True">Ok</Button>

没关系,直到我CommandButtonie中添加一个

<Button x:Name="buttonOk" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>

现在大概无法关闭,因为我正在处理Command。我可以通过插入EventHandler并调用this.Close()即解决此问题

<Button x:Name="buttonOk" 
        Click="closeWindow" 
        Command="{Binding SaveCommand}" 
        IsCancel="True">Ok</Button>

但现在我的代码后面有代码,即方法SaveCommand。我正在使用MVVM模式,这SaveCommand是我的代码后面唯一的代码。

我该怎么做才能不使用后面的代码?


16
注意-将IsCancel = "True"“确定”按钮设置为一个好主意。该属性用于“取消”按钮。
格雷格D

Answers:


61

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

在ViewModel中:

public Action CloseAction  { get; set;}

并在View构造函数中:

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

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

CloseAction(); // Calls Close() method of the View

这对我有用,似乎是一个非常优雅的解决方案,并且为我节省了很多代码。


这对我不起作用。当我调用CloseAction()时,尽管View中有代码,但它说CloseAction为null
Danielle

11
请原谅我的无知,但是这不违反将View和ViewModel分离的原理吗?如果要在视图中实例化ViewModel,则最好不要使用MVVM。我认为最佳实践是分别实例化View和ViewModel并将DataContext设置为视图本身之外的View。
saegeoff 2014年

2
通过使Action为静态属性来解决它。天哪!
Talal Yousif 2015年

11
我意识到这已经很老了,但是我认为除非有我不知道的严格定义,否则这种方法不会破坏MVVM。最终,MVVM要求VM不知道该视图,但是视图必须知道该VM。如果要替换视图,则不会以任何方式破坏VM。会有一个未实例化的动作,但是我不认为这是MVVM规则被破坏的声明。搜索“ WPF DataContext实例化”将在许多文章中提出这种方法。
flyNflip

6
您可以执行构造函数注入而不是属性注入来摆脱null检查:programms.stackexchange.com/questions/177649/… this.DataContext = new ViewModel(this.Close);,然后在ViewModel的构造函数中分配接近CloseAction的值。这还具有使CloseAction变为只读的优点。
DharmaTurtle '16

20

MVVM的使用方法很干净,InteractionTrigger并且CallMethodActionMicrosoft.Interactivity.Core

您将需要添加一个新的命名空间,如下所示

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

您将需要Microsoft.Xmal.Behaviours.Wpf程序集,然后下面的xaml代码将起作用。

<Button Content="Save" Command="{Binding SaveCommand}">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
      <i:CallMethodAction MethodName="Close"
                           TargetObject="{Binding RelativeSource={RelativeSource
                                                  Mode=FindAncestor,
                                                  AncestorType=Window}}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Button>

您不需要后面的任何代码,也可以调用的任何其他方法Window


到目前为止,这是我所见过的最干净的方法,它没有任何代码,也没有将ViewModel耦合到View。它也适用于命令。您将需要部署几个额外的DLL,并且如果您希望能够从命令中取消关闭,则将需要额外的工作。与在代码后面有一个Click事件并仅调用Close()并没有什么不同,事件处理程序后面的代码将使处理close命令取消close事件的情况变得更容易(例如,如果保存了错误)数据)。感谢Massimiliano
理查德·摩尔

1
Rajnikant的代码在VS 2019中不再可用,因为MS使WPF行为成为开源并转移到Microsoft.Xaml.Behaviors.Wpf NuGet包中。信息来源是要发表的评论:developercommunity.visualstudio.com/content/problem/198075/…。详细步骤在重构你的代码是:devblogs.microsoft.com/dotnet/...
埃里克·伍德

它比较干净,但是我认为它应该由视图模型而不是视图控制,因为它属于命令而不是标准的关闭按钮。
丹尼尔·莫勒

17

正如有人评论的那样,我发布的代码不是MVVM友好的,第二种解决方案呢?

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

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>

ViewModel:

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

第二个可能更好的解决方案: 使用附加行为

XAML

<Button Content="Ok and Close" Command="{Binding OkCommand}" b:CloseOnClickBehaviour.IsEnabled="True" />

查看模型

public ICommand OkCommand
{
    get { return _okCommand; }
}

行为类与此类似:

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();
    }

}

33
您永远不要重复我,永远不要将Window与ViewModel耦合。现在写一句话100个实例:)
Ignacio Soler Garcia 2012年

4
+1恕我直言,这是最好的解决方案:它能做的事情,最短的时间,不需要复杂的基础结构,以MVVM的方式解决了问题。@SoMoS-这里没有耦合。完全没有 VM不知道View的存在;该命令将Window作为参数,因为它需要知道要关闭什么。
伊利亚·巴拉霍夫斯基

2
+1 @SoMoS我同意Ilia,这正是解耦的解决方案,我不会将保存和关闭窗口逻辑结合在一起,但这是另一回事
makc

8
@Barahovski:窗口是WPF对象。视图模型不应依赖WPF或任何繁重的框架。单元测试(没有UI)如何获取Window实例对此进行测试?
g.pickardou 2014年

@IgnacioSolerGarcia +1您的评论。也许附加的行为方法是更好的解决方案?我已经用它更新了答案。
Simone 2015年

13

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

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();
            };
        }
    }

然后,您可以将其附加到您的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>

我已经添加了CommandCommandParameter因此您可以在Window关闭之前运行命令。


1
我参加这个聚会有点晚了,但是可以通过将行为直接放在Button上来进一步简化。您可以为Click事件定义一个处理程序,该处理程序调用Window.GetWindow(AssociatedObject)?.Close()(当然要进行适当的null检查),该事件在OnAttachedOnDetaching挂钩的替代项中附加/分离。三个琐碎的功能,零属性,并且可以在相同(或不同)窗口中附加到任意数量的按钮。
bionicOnion

嗯,如果不是将行为附加到按钮并传递窗口,而不是将行为附加到窗口并传递按钮,这将不是一个更好的设计?
索伦Kuklau

9

对于小型应用程序,我使用自己的应用程序控制器来显示,关闭和处置窗口和DataContext。这是应用程序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();              
    }
}

及其从ViewModels的调用:

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

// Close Current Window from viewModel
ApplicationController.Instance.CloseWindow(this);

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


7

我试图以某种通用的MVVM方法解决此问题,但我总是发现自己最终会遇到不必要的复杂逻辑。为了实现接近的行为,我从没有代码落后的规则中排除了一个例外,并且仅在代码背后使用了良好的ol'事件:

XAML:

<Button Content="Close" Click="OnCloseClicked" />

后面的代码:

private void OnCloseClicked(object sender, EventArgs e)
{
    Visibility = Visibility.Collapsed;
}

尽管我希望使用命令/ MVVM可以更好地支持此功能,但我只是认为没有比使用事件更简单,更清晰的解决方案了。


6

我将“发布订阅”模式用于复杂的类相关性:

ViewModel:

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

窗口:

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

    public SomeWindow()
    {
        InitializeComponent();

        _subscription.Subscribe(Messages.REQUEST_DEPLOYMENT_SETTINGS_CLOSED, obj =>
            {
                this.Close();
            });
    }
}

您可以利用Bizmonger.Patterns获取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
}

}

订阅

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);
    }
}

4

此任务有一个有用的行为,它不会破坏MVVM,即Expression Blend 3引入的一种行为,以允许View挂接到完全在ViewModel中定义的命令中。

此行为演示了一种简单的技术,该技术允许ViewModel在Model-View-ViewModel应用程序中管理View的关闭事件。

这使您可以在视图(UserControl)中挂钩行为,该行为将提供对控件窗口的控制,从而允许ViewModel控制是否可以通过标准ICommands关闭窗口。

使用行为允许ViewModel管理MV-VM中的视图生命周期

http://gallery.expression.microsoft.com/WindowCloseBehavior/

以上链接已存档到http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content


4

我在这个主题上苦苦挣扎了一段时间,最终采用了仍然与MVVM一致的最简单方法:让按钮执行执行所有繁重任务的Command,并让按钮的Click处理程序关闭窗口。

XAML

<Button x:Name="buttonOk" 
        Click="closeWindow" 
        Command="{Binding SaveCommand}" />

XAML.cs

public void closeWindow() 
{
    this.DialogResult = true;
}

SaveCommand.cs

 // I'm in my own file, not the code-behind!

没错,仍然有代码隐藏,但是这并没有本质上的坏处。从面向对象的角度来看,只告诉窗口自己关闭,这对我来说最有意义。


4

我们在.xaml定义中具有name属性:

x:Name="WindowsForm"

然后我们有一个按钮:

<Button Command="{Binding CloseCommand}" 
CommandParameter="{Binding ElementName=WindowsForm}" />

然后在ViewModel中:

public DelegateCommand <Object>  CloseCommand { get; private set; }

Constructor for that view model:
this.CloseCommand = new DelegateCommand<object>(this.CloseAction);

然后,最后是action方法:

private void CloseAction (object obj)
{
  Window Win = obj as Window;
  Win.Close();

}

我使用此代码从应用程序中关闭了一个弹出窗口。


2

我发现自己必须在基于.Net Core 3.0的WPF应用程序上执行此操作,不幸的是,Microsoft.Xaml.Behaviors.Wpf NuGet程序包中尚未正式提供行为支持。

相反,我选择了使用Façade设计模式的解决方案。

接口:

public interface IWindowFacade
{
    void Close();
}

窗口:

public partial class MainWindow : Window, IWindowFacade

视图模型上的标准命令属性:

public ICommand ExitCommand
…

控件绑定:

<MenuItem Header="E_xit" Command="{Binding ExitCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>

命令:

public class ExitCommand : ICommand
{
    …
    public void Execute(object parameter)
    {
        var windowFacade = parameter as IWindowFacade;
        windowFacade?.Close();
    }
    …
}

由于该Close()方法已经由Window该类实现,因此将façade接口应用于窗口是UI层中仅需的代码(对于此简单示例)。表示层中的命令避免了对视图/ UI层的任何依赖,因为它不知道在调用Close立面上的方法时在说什么。


2

在您当前的窗口xaml.cs文件中,调用以下代码:

var curWnd = Window.GetWindow(this); // passing current window context
curWnd?.Close();

这应该做的事情。
它对我有用,希望对您也一样)


1

我在Silverlight中有以下解决方案。也将在WPF中。

ChildWindowExt.cs:

namespace System.Windows.Controls
{
    public class ChildWindowExt : ChildWindow
    {
        public static readonly DependencyProperty IsOpenedProperty =
          DependencyProperty.Register(
          "IsOpened",
          typeof(bool),
          typeof(ChildWindowExt),
          new PropertyMetadata(false, IsOpenedChanged));

        private static void IsOpenedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue == false)
            {
                ChildWindowExt window = d as ChildWindowExt;
                window.Close();
            }
            else if ((bool)e.NewValue == true)
            {
                ChildWindowExt window = d as ChildWindowExt;
                window.Show();
            }
        }

        public bool IsOpened
        {
            get { return (bool)GetValue(IsOpenedProperty); }
            set { SetValue(IsOpenedProperty, value); }
        }

        protected override void OnClosing(ComponentModel.CancelEventArgs e)
        {
            this.IsOpened = false;
            base.OnClosing(e);
        }

        protected override void OnOpened()
        {
            this.IsOpened = true;
            base.OnOpened();
        }
    }
}

ItemWindow.xaml:

<extControls:ChildWindowExt  
    x:Class="MyProject.ItemWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:extControls="clr-namespace:System.Windows.Controls"
    Title="{Binding Title}" IsOpened="{Binding IsOpened, Mode=TwoWay}" Width="640" Height="480">

    <Grid x:Name="LayoutRoot">
        <Button Command="{Binding UpdateCommand}" Content="OK" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>

</extControls:ChildWindowExt>

ItemViewModel.cs:

private bool _IsOpened;
public bool IsOpened
{
    get
    {
        return _IsOpened;
    }
    set
    {
        if (!Equals(_IsOpened, value))
        {
            _IsOpened = value;
            RaisePropertyChanged("IsOpened");
        }
    }
}

private RelayCommand _UpdateCommand;
/// <summary>
/// Insert / Update data entity
/// </summary>
public RelayCommand UpdateCommand
{
    get
    {
        if (_UpdateCommand == null)
        {
            _UpdateCommand = new RelayCommand(
                () =>
                {
                    // Insert / Update data entity
                    ...

                    IsOpened = false;
                },
                () =>
                {
                    return true;
                });
        }
        return _UpdateCommand;
    }
}

ItemsViewModel.cs:

    private RelayCommand _InsertItemCommand;
    /// <summary>
    /// 
    /// </summary>
    public RelayCommand InsertItemCommand
    {
        get
        {
            if (_InsertItemCommand == null)
            {
                _InsertItemCommand = new RelayCommand(
                    () =>
                    {
                        ItemWindow itemWin = new ItemWindow();
                        itemWin.DataContext = new ItemViewModel();
                        itemWin.Show();

                        // OR

                        // ItemWindow itemWin = new ItemWindow();
                        // ItemViewModel newItem = new ItemViewModel();
                        // itemWin.DataContext = newItem;
                        // newItem.IsOpened = true;

                    },
                    () =>
                    {
                        return true;
                    });
            }
            return _InsertItemCommand;
        }
    }

MainPage.xaml:

<Grid x:Name="LayoutRoot">
    <Button Command="{Binding InsertItemCommand}" Content="Add New" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center" />
</Grid>

祝大家有个好主意和项目;-)



1

我认为(几乎)尚未包括最简单的方法。与其使用添加新依赖项的“行为”,不如使用附加属性:

    using System;
    using System.Windows;
    using System.Windows.Controls;

    public class DialogButtonManager
    {
        public static readonly DependencyProperty IsAcceptButtonProperty = DependencyProperty.RegisterAttached("IsAcceptButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsAcceptButtonPropertyChanged));
        public static readonly DependencyProperty IsCancelButtonProperty = DependencyProperty.RegisterAttached("IsCancelButton", typeof(bool), typeof(DialogButtonManager), new FrameworkPropertyMetadata(OnIsCancelButtonPropertyChanged));

        public static void SetIsAcceptButton(UIElement element, bool value)
        {
            element.SetValue(IsAcceptButtonProperty, value);
        }

        public static bool GetIsAcceptButton(UIElement element)
        {
            return (bool)element.GetValue(IsAcceptButtonProperty);
        }

        public static void SetIsCancelButton(UIElement element, bool value)
        {
            element.SetValue(IsCancelButtonProperty, value);
        }

        public static bool GetIsCancelButton(UIElement element)
        {
            return (bool)element.GetValue(IsCancelButtonProperty);
        }

        private static void OnIsAcceptButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = sender as Button;

            if (button != null)
            {
                if ((bool)e.NewValue)
                {
                    SetAcceptButton(button);
                }
                else
                {
                    ResetAcceptButton(button);
                }
            }
        }

        private static void OnIsCancelButtonPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            Button button = sender as Button;

            if (button != null)
            {
                if ((bool)e.NewValue)
                {
                    SetCancelButton(button);
                }
                else
                {
                    ResetCancelButton(button);
                }
            }
        }

        private static void SetAcceptButton(Button button)
        {
            Window window = Window.GetWindow(button);
            button.Command = new RelayCommand(new Action<object>(ExecuteAccept));
            button.CommandParameter = window;
        }

        private static void ResetAcceptButton(Button button)
        {
            button.Command = null;
            button.CommandParameter = null;
        }

        private static void ExecuteAccept(object buttonWindow)
        {
            Window window = (Window)buttonWindow;

            window.DialogResult = true;
        }

        private static void SetCancelButton(Button button)
        {
            Window window = Window.GetWindow(button);
            button.Command = new RelayCommand(new Action<object>(ExecuteCancel));
            button.CommandParameter = window;
        }

        private static void ResetCancelButton(Button button)
        {
            button.Command = null;
            button.CommandParameter = null;
        }

        private static void ExecuteCancel(object buttonWindow)
        {
            Window window = (Window)buttonWindow;

            window.DialogResult = false;
        }
    }

然后只需在对话框按钮上进行设置即可:

<UniformGrid Grid.Row="2" Grid.Column="1" Rows="1" Columns="2" Margin="3" >
    <Button Content="Accept" IsDefault="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsAcceptButton="True" />
    <Button Content="Cancel" IsCancel="True" Padding="3" Margin="3,0,3,0" DialogButtonManager.IsCancelButton="True" />
</UniformGrid>

1

我还必须处理这个问题,所以这里是我的解决方案。这对我很有效。

1.创建类DelegateCommand

    public class DelegateCommand<T> : ICommand
{
    private Predicate<T> _canExecuteMethod;
    private readonly Action<T> _executeMethod;
    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<T> executeMethod) : this(executeMethod, null)
    {
    }
    public DelegateCommand(Action<T> executeMethod, Predicate<T> canExecuteMethod)
    {
        this._canExecuteMethod = canExecuteMethod;
        this._executeMethod = executeMethod ?? throw new ArgumentNullException(nameof(executeMethod), "Command is not specified."); 
    }


    public void RaiseCanExecuteChanged()
    {
        if (this.CanExecuteChanged != null)
            CanExecuteChanged(this, null);
    }
    public bool CanExecute(object parameter)
    {
        return _canExecuteMethod == null || _canExecuteMethod((T)parameter) == true;
    }

    public void Execute(object parameter)
    {
        _executeMethod((T)parameter);
    }
}

2.定义您的命令

        public DelegateCommand<Window> CloseWindowCommand { get; private set; }


    public MyViewModel()//ctor of your viewmodel
    {
        //do something

        CloseWindowCommand = new DelegateCommand<Window>(CloseWindow);


    }
        public void CloseWindow(Window win) // this method is also in your viewmodel
    {
        //do something
        win?.Close();
    }

3.在视图中绑定命令

public MyView(Window win) //ctor of your view, window as parameter
    {
        InitializeComponent();
        MyButton.CommandParameter = win;
        MyButton.Command = ((MyViewModel)this.DataContext).CloseWindowCommand;
    }

4.现在窗口

  Window win = new Window()
        {
            Title = "My Window",
            Height = 800,
            Width = 800,
            WindowStartupLocation = WindowStartupLocation.CenterScreen,

        };
        win.Content = new MyView(win);
        win.ShowDialog();

这样,您还可以将命令绑定到xaml文件中,并使用FindAncestor查找窗口并将其绑定到命令参数。


0

我一直在寻找解决同一问题的方法,发现执行以下操作可以正常工作。该解决方案类似于OP在他的问题中提到的内容,但有一些区别:

  1. 不需要IsCancel财产。

  2. 后面的代码不应关闭窗口。刚设定DialogResult

就我而言,它首先在后面执行代码,然后查看绑定到按钮的模型命令。

XAML

<Button x:Name="buttonOk" Click="Save_Click" Command="{Binding SaveCommand}">OK</Button>

背后的代码

private void Apply_OnClick(object sender, RoutedEventArgs e)
{
    this.DialogResult = true;
}

查看模型

private void Save()
{
 // Save data.
}

希望这可以帮助。


0

您可以改写这个问题,然后这样做-提出另一个解决方案。如何在MVVM环境中启用视图,视图模型和其他视图之间的通信?您可以使用Mediator模式。它基本上是一个通知系统。对于实际的Mediator实施,请使用google或询问我,我可以通过电子邮件发送。

发出一个旨在关闭视图的命令。

public void Execute( object parameter )
{
    this.viewModel.DisposeMyStuff();
    Mediator.NotifyColleagues(Mediator.Token.ConfigWindowShouldClose);
}

调解员将提出通知(令牌)

在View构造函数的后面侦听此通知(令牌):

public ClientConfigView()
{
    InitializeComponent();
    Mediator.ListenOn(Mediator.Token.ConfigWindowShouldClose, callback => this.Close() );
}

0

在WPF中关闭对我有用的窗口的解决方案在这里没有得到答案,所以我想我也会添加我的解决方案。

        private static Window GetWindow(DependencyObject sender)
        {
            Window window = null;
            if (sender is Window)
                window = (Window)sender;
            if (window == null)
                window = Window.GetWindow(sender);
            return window;
        }
        private void CloseWindow(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender as DependencyObject;

            Window window = GetWindow(button);
                if (window != null)
                    window.Close();
                   // window.Visibility = Visibility.Hidden; 
           // choose between window.close or set window.visibility to close or hide the window.

            //            }
        }

将CloseWindow事件添加到窗口中的按钮,如下所示。

<Button Content="Cancel" Click="CloseWindow" >

不想太粗鲁,但是当您在窗口本身背后的代码中处理了Click事件时,可以通过调用Close()直接关闭窗口。您无需搜索Button的Window父级即可将其关闭。您的答案与MVVM无关,您的代码可以恢复为:private void CloseWindow(object sender,RoutedEventArgs e){Close(); }
Alexandru Dicu

0

简单的方法是在saveComand实现上关闭窗口。使用下面的代码关闭窗口。

Application.Current.Windows[1].Close();

它将关闭子窗口。


-2

您可以在没有代码的情况下进行操作。创建命令,在Execute方法中,在viewmodel上调用“保存”方法,然后在编辑窗口中调用close方法,您可以通过参数将其传递给命令:

public void Execute(object parameter)
{
    _mainViewModel.SaveSomething();
    var editWindow = parameter as MyEditWindow;
    editWindow?.Close();
}

保存并关闭按钮XAML:

<Button Content"Save&Close" Command="{Binding SaveCmd}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"  IsDefault="True" />

后面的代码比在这样的视图模型中包含视图实例更好。问题是关于以下MVVM模式。
亚历山德鲁·迪库
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.