WPF MVVM: How to close a window

后端 未结 21 1718
后悔当初
后悔当初 2020-12-04 08:19

I have a Button that closes my window when it\'s clicked:


相关标签:
21条回答
  • 2020-12-04 08:52

    I think the most simple way has not been included already (almost). Instead of using Behaviours which adds new dependencies just use attached properties:

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

    Then just set it on your dialog buttons:

    <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>
    
    0 讨论(0)
  • 2020-12-04 08:56

    I've tried to resolve this issue in some generic, MVVM way, but I always find that I end up unnecessary complex logic. To achieve close behavior I have made an exception from the rule of no code behind and resorted to simply using good ol' events in code behind:

    XAML:

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

    Code behind:

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

    Although I wish this would be better supported using commands/MVVM, I simply think that there is no simpler and more clear solution than using events.

    0 讨论(0)
  • 2020-12-04 08:57

    In your current window xaml.cs file, call the below code:

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

    This should do the thing.
    It worked for me, hope will do the same for you )

    0 讨论(0)
  • 2020-12-04 08:58

    For small apps, I use my own Application Controller for showing, closing and disposing windows and DataContexts. It's a central point in UI of an application.

    It's something like this:

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

    and their invocations from ViewModels:

    // Show new Window with DataContext
    ApplicationController.Instance.ShowNewWindow(
                    new ClientCardsWindow(),
                    new ClientCardsVM(),
                    false);
    
    // Close Current Window from viewModel
    ApplicationController.Instance.CloseWindow(this);
    

    Of course you can find some restrictions in my solution. Again: I use it for small projects, and it's enough. If you're interested, I can post full code here or somewhere else/

    0 讨论(0)
  • 2020-12-04 08:58

    I found myself having to do this on a WPF application based on .Net Core 3.0, where unfortunately behaviour support was not yet officially available in the Microsoft.Xaml.Behaviors.Wpf NuGet package.

    Instead, I went with a solution that made use of the Façade design pattern.

    Interface:

    public interface IWindowFacade
    {
        void Close();
    }
    

    Window:

    public partial class MainWindow : Window, IWindowFacade
    …
    

    Standard command property on the view model:

    public ICommand ExitCommand
    …
    

    Control binding:

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

    Command:

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

    Because the Close() method is already implemented by the Window class, applying the façade interface to the window is the only required code behind in the UI layer (for this simple example). The command in the presentation layer avoids any dependencies on the view/UI layer as it has no idea what it is talking to when it calls the Close method on the façade.

    0 讨论(0)
  • 2020-12-04 08:59

    As someone commented, the code I have posted is not MVVM friendly, how about the second solution?

    1st, not MVVM solution (I will not delete this as a reference)

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

    2nd, probably better solution: Using attached behaviours

    XAML

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

    View Model

    public ICommand OkCommand
    {
        get { return _okCommand; }
    }
    

    Behaviour Class Something similar to this:

    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();
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题