WPF MVVM: как закрыть окно - PullRequest
       15

WPF MVVM: как закрыть окно

64 голосов
/ 07 декабря 2010

У меня есть Button, который закрывает мое окно при нажатии:

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

Это нормально, пока я не добавлю Command к Button, т.е.

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

Теперь он не закрывается, предположительно, потому что я обрабатываю Command. Я могу это исправить, введя EventHandler и позвонив this.Close() т.е. 1013 *

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

но теперь у меня есть код в моем коде, то есть метод SaveCommand. Я использую шаблон MVVM, и SaveCommand - единственный код в моем коде.

Как я могу сделать это по-другому, чтобы не использовать код позади?

Ответы [ 19 ]

52 голосов
/ 30 июня 2013

Я только что закончил запись в блоге на эту тему. В двух словах, добавьте свойство Action к вашей ViewModel с помощью get и set методов доступа. Затем определите 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

Это сработало для меня, показалось довольно элегантным решением и сэкономило мне кучу кода.

22 голосов
/ 07 декабря 2010

К сожалению, отображение окон - это реальная проблема в MVVM, поэтому вам нужно немного поработать над инфраструктурой или использовать инфраструктуру MVVM, например Cinch . Если вы хотите потратить время, чтобы сделать это самостоятельно , вот ссылка на то, как Cinch делает это.

Хорошо, что вы пытаетесь скрыть какую-то логику, но на самом деле это не конец света, если вы это сделаете. В данном случае это не похоже на то, что это вызовет слишком много проблем.

15 голосов
/ 25 января 2012

Как кто-то прокомментировал, код, который я разместил, не является дружественным к 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; }

2-е, возможно, лучшее решение: Использование вложенных поведений

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

}
12 голосов
/ 25 января 2012

Я бы лично использовал поведение, чтобы делать такие вещи:

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

Затем вы можете прикрепить это к своим Window и Button, чтобы выполнить работу:

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

Я добавил сюда Command и CommandParameter, чтобы вы могли выполнить команду до закрытия Window.

8 голосов
/ 21 января 2016

Очень чистый и MVVM способ использовать InteractionTrigger и CallMethodAction, определенные в Microsoft.Interactivity.Core

Вам нужно будет добавить два пространства имен, как показано ниже

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

И сборки System.Windows.Interactivity и Microsoft.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>

Вам не нужен какой-либо код или что-то еще, и вы также можете вызвать любой другой метод Window.

7 голосов
/ 07 декабря 2010

Для небольших приложений я использую свой собственный Application Controller для отображения, закрытия и удаления окон и DataContexts. Это центральная точка в пользовательском интерфейсе приложения.

Это примерно так:

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

Конечно, вы можете найти некоторые ограничения в моем решении. Опять же: я использую это для небольших проектов, и этого достаточно. Если вам интересно, я могу выложить полный код здесь или где-нибудь еще /

5 голосов
/ 20 февраля 2015

Я использую шаблон Опубликовать подписку для сложных зависимостей классов:

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.

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);
    }
}
5 голосов
/ 02 мая 2013

Я пытался решить эту проблему каким-то общим способом MVVM, но я всегда нахожу, что в итоге получаю ненужную сложную логику.Чтобы добиться близкого поведения, я сделал исключение из правила отсутствия кода и прибегнул к простому использованию хороших старых событий в коде:

XAML:

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

Код сзади:

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

Хотелось бы, чтобы это лучше поддерживалось с помощью команд / MVVM, я просто думаю, что не существует более простого и понятного решения, чем использование событий.

3 голосов
/ 26 июня 2012

Для этой задачи есть полезное поведение, которое не нарушает MVVM, поведение, введенное в Expression Blend 3, чтобы позволить View подключаться к командам, полностью определенным в ViewModel.

Это поведение демонстрирует простую технику для ViewModel для управления событиями закрытия View в Приложение Model-View-ViewModel.

Это позволяет вам подключить поведение в вашем View (UserControl), которое обеспечит контроль над окном элемента управления, позволяя ViewModel контролировать, можно ли закрыть окно с помощью стандартных ICommands.

Использование поведений, позволяющих ViewModel управлять временем жизни представления в M-V-VM

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

вышеуказанная ссылка была заархивирована на http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content

1 голос
/ 21 марта 2016

У нас есть свойство name в определении .xaml:

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

Затем, наконец, метод действия:

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

}

Я использовал этот код, чтобы закрыть всплывающее окно из приложения ..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...