WPF MVVM: Як закрити вікно


78

У мене є Buttonщо закриває моє вікно після натискання:

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

Це добре, поки я не додаю a Commandдо Buttonie

<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"кнопки OK - погана ідея. Ця властивість призначена для кнопок Скасувати.
Greg D

Відповіді:


62

Я щойно заповнив допис у блозі на цю саму тему. У двох словах, додайте 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

Це спрацювало для мене, здавалося досить елегантним рішенням і врятувало купу кодування.


Для мене це не працює. Коли я закликаю CloseAction (), це говорить, що CloseAction є нульовим, незважаючи на код у поданні
Danielle

11
Вибачте за моє незнання, але як це не порушує принципу роз'єднання View та ViewModel? Якщо ви створюєте екземпляр вашого ViewModel у своєму поданні, ви також можете не використовувати MVVM. Я вважаю, що найкращою практикою є створення екземплярів вашого View та ViewModel окремо та встановлення DataContext на View поза самим видом.
відступ

2
Вирішив це, зробивши Action статичним властивістю. Ага!
Талал Йосиф

12
Я усвідомлюю, що це старіє, але я б стверджував, що цей метод не порушує MVVM, якщо немає чіткого визначення, якого я не знаю. Зрештою, MVVM вимагає, щоб віртуальна машина не знала про вигляд, але вигляд повинен знати про цю віртуальну машину. Якби хтось замінив вигляд, це ніяк не порушило б віртуальну машину. Була б необгрунтована Акція, але я не думаю, що це декларація про порушення норм MVVM. Пошук "WPF DataContext Instantiation" підніме саме цей метод у багатьох статтях.
flyNflip

6
Ви можете зробити інжекцію конструктора замість ін’єкції властивостей, щоб позбутися нульової перевірки: programmers.stackexchange.com/questions/177649/…, this.DataContext = new ViewModel(this.Close); а потім у конструкторі ViewModel ви призначаєте близько CloseAction. Це також має перевагу в тому, що CloseAction отримує лише для користувачів.
DharmaTurtle

22

Дуже чистий і MVVM спосіб полягає у використанні InteractionTriggerта CallMethodActionвизначенні вMicrosoft.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 (наприклад, якщо під час збереження помилки дані). Подяка Массіміліано
Річард Мур,

2
Код Раджніканта довше працює у VS 2019, оскільки MS зробив поведінку WPF з відкритим кодом і перемістив до пакета Microsoft.Xaml.Behaviors.Wpf NuGet. Джерелом інформації є коментарі до випуску: developercommunity.visualstudio.com/content/problem/198075/… . Детальні кроки щодо рефакторингу коду наведено за адресою: devblogs.microsoft.com/dotnet/…
Ерік Вуд,

Він чистіший, але я вважаю, що цим повинен керувати модель перегляду, а не вигляд, оскільки він належить команді, а не стандартній кнопці закриття.
Даніель Меллер

17

Як хтось коментував, код, який я розмістив, не є зручним для MVVM, як щодо другого рішення?

По-перше, не 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
Ви ніколи, повторіть зі мною, ніколи не поєднуєте Вікно з ViewModel. Тепер напишіть речення 100 випадків :)
Ігнасіо Солер Гарсія

4
+1 IMHO це найкраще рішення: воно робить все, воно найкоротше, не вимагає складної інфраструктури, вирішує проблему способами MVVM. @SoMoS - тут немає зв'язку. Зовсім. ВМ не знає про існування View; команда отримує Window як параметр, оскільки вона повинна знати, що закрити.
Ilia Barahovski

2
+1 @SoMoS Я згоден з Ілією, це саме розв’язане рішення, я б не поєднував логіку збереження та закриття вікна разом, але це вже інша справа
makc

8
@Barahovski: Вікно - це об'єкт WPF. Модель перегляду не повинна залежати від WPF або будь-якого важкого фреймворку. Як можна модульним тестом (без інтерфейсу користувача) отримати екземпляр Window для перевірки цього?
g.pickardou

@IgnacioSolerGarcia +1 до вашого коментаря. можливо, підхід до поведінки є кращим рішенням? Я оновив свою відповідь.
Сімоне

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

Потім ви можете прикріпити це до свого 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закриття.


1
Я трохи запізнився з цією вечіркою, але це можна ще спростити, поклавши поведінку безпосередньо на кнопку. Ви можете визначити обробник для події Click, який викликає Window.GetWindow(AssociatedObject)?.Close()(звичайно, з відповідними нульовими перевірками), який приєднується / від'єднується в перевизначеннях для хуків OnAttachedта OnDetaching. Три тривіальні функції, нульові властивості, і можуть бути приєднані до довільної кількості кнопок в одних і тих же (або різних) вікнах.
bionicOnion

Хм, хіба це не буде кращим дизайном, якби замість того, щоб прикріпити поведінку до вікна і передати кнопку, ви могли б прикріпити поведінку до кнопки і передати вікно?
Sören Kuklau

9

Для невеликих додатків я використовую власний контролер додатків для показу, закриття та утилізації вікон та 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);

Звичайно, ви можете знайти деякі обмеження в моєму рішенні. Знову ж таки: я використовую його для невеликих проектів, і цього досить. Якщо вам цікаво, я можу розмістити повний код тут або десь ще /


7

Я намагався вирішити цю проблему якимось загальним чином, MVVM, але я завжди виявляю, що в підсумку виявляю непотрібну складну логіку. Для досягнення близької поведінки я зробив виняток із правила про відсутність коду і вдався до простого використання хороших подій ol в коді позаду:

XAML:

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

Код позаду:

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

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


6

Я використовую шаблон Publish Subscribe для складних залежностей класу:

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

4

Для цього завдання є корисна поведінка, яка не порушує MVVM, поведінку, представлену із Expression Blend 3, щоб дозволити поданню переглядати команди, повністю визначені в ViewModel.

Ця поведінка демонструє просту техніку, яка дозволяє ViewModel керувати закритими подіями подання у програмі Model-View-ViewModel.

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

Використання поведінки, щоб дозволити ViewModel керувати тривалістю перегляду в MV-VM

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

Вище посилання заархівовано на http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content


4

Я боровся з цією темою деякий час, і врешті-решт пішов із найпростішим підходом, який досі відповідає MVVM: нехай кнопка виконує команду, яка виконує всі важкі дії, а обробник клацання кнопки закриває вікно.

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

Ми маємо властивість 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();

}

Я використав цей код, щоб закрити спливаюче вікно програми.


2

Мені довелося робити це в додатку WPF на базі .Net Core 3.0, де, на жаль, підтримка поведінки ще не була офіційно доступною в пакеті Microsoft.Xaml.Behaviors.Wpf NuGet.

Натомість я запропонував рішення, яке використовувало шаблон дизайну фасадів.

Інтерфейс:

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класом, застосування фасадного інтерфейсу до вікна є єдиним необхідним кодом в рівні інтерфейсу користувача (для цього простого прикладу). Команда на рівні презентації дозволяє уникнути будь-яких залежностей на рівні view / 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? Ви можете використати шаблон Посередника. В основному це система сповіщень. Що стосується фактичної реалізації Медіатора, загуглюйте її або попросіть мене, і я можу надіслати її електронною поштою.

Створіть команду, метою якої є закриття подання.

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

Посередник підніме повідомлення (токен)

Прослухайте це сповіщення (маркер) таким чином у конструкторі View codebehind:

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 (). Вам не потрібно шукати батьківського вікна кнопки, щоб закрити його. Ваша відповідь не має нічого спільного з MVVM, і ваш код можна відновити до: private void CloseWindow (відправник об’єкта, RoutedEventArgs e) {Close (); }
Александру Діку

0

Простий підхід - це закрите вікно впровадження saveComand. Використовуйте код нижче, щоб закрити вікно.

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

Це закриє вікно дитини.


-2

Ви можете зробити це без коду позаду. Створіть команду, у методі Виконання викличте метод "Зберегти" на viewmodel, а після цього виклику закрийте метод у вікні редагування, який ви можете передати команді за параметром:

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.
Александру Діку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.