WPF MVVM: So schließen Sie ein Fenster


78

Ich habe eine Button, die mein Fenster schließt, wenn es angeklickt wird:

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

Das ist in Ordnung, bis ich ein Commandzum Buttonie hinzufüge

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

Jetzt schließt es vermutlich nicht, weil ich das handhabe Command. Ich kann dies beheben, indem ich ein EventHandlerEingabe this.Close()mache und z

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

aber jetzt habe ich Code in meinem Code hinter dh der Methode SaveCommand. Ich verwende das MVVM-Muster und SaveCommandist der einzige Code in meinem Code dahinter.

Wie kann ich das anders machen, um keinen Code dahinter zu verwenden?


16
Hinweis: Das Einstellen IsCancel = "True"einer OK-Taste ist eine schlechte Idee. Diese Eigenschaft gilt für Schaltflächen zum Abbrechen.
Greg D

Antworten:


61

Ich habe gerade einen Blog-Beitrag zu diesem Thema fertiggestellt. Fügen Sie ActionIhrem ViewModel mit getund setAccessoren eine Eigenschaft hinzu . Definieren Sie dann die Actionvon Ihrem ViewKonstruktor. Rufen Sie abschließend Ihre Aktion im gebundenen Befehl auf, der das Fenster schließen soll.

Im ViewModel:

public Action CloseAction  { get; set;}

und im ViewKonstruktor:

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

Schließlich können wir in jedem gebundenen Befehl, der das Fenster schließen soll, einfach aufrufen

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

Dies funktionierte für mich, schien eine ziemlich elegante Lösung zu sein und ersparte mir eine Menge Codierung.


Das funktioniert bei mir nicht. Wenn ich CloseAction () aufrufe, heißt es, dass CloseAction trotz des Codes in der Ansicht
Danielle

11
Entschuldigen Sie meine Unwissenheit, aber wie verstößt dies nicht gegen das Prinzip der Entkopplung von View und ViewModel? Wenn Sie Ihr ViewModel in Ihrer Ansicht instanziieren, können Sie MVVM auch nicht verwenden. Ich denke, die beste Vorgehensweise besteht darin, Ihre Ansicht und Ihr ViewModel einzeln zu instanziieren und den DataContext auf die Ansicht außerhalb der Ansicht selbst zu setzen.
Saegeoff

2
Es wurde behoben, indem die Aktion zu einer statischen Eigenschaft gemacht wurde. Daaaah!
Talal Yousif

11
Mir ist klar, dass dies alt wird, aber ich würde argumentieren, dass diese Methode MVVM nicht bricht, es sei denn, es gibt eine strenge Definition, die mir nicht bekannt ist. Letztendlich erfordert MVVM, dass die VM die Ansicht nicht kennt, die Ansicht jedoch die VM kennt. Wenn man die Ansicht ersetzen würde, würde dies die VM in keiner Weise beschädigen. Es würde eine unbegründete Aktion geben, aber ich glaube nicht, dass dies eine Erklärung dafür ist, dass MVVM-Regeln verletzt werden. Wenn Sie nach "WPF DataContext Instantiation" suchen, wird genau diese Methode in vielen Artikeln angezeigt.
flyNflip

6
Sie können eine Konstruktorinjektion anstelle einer Eigenschaftsinjektion durchführen, um die Nullprüfung zu beseitigen : programmers.stackexchange.com/questions/177649/…. this.DataContext = new ViewModel(this.Close); Anschließend weisen Sie im ViewModel-Konstruktor CloseAction zu. Dies hat auch den Vorteil, dass CloseAction nur verfügbar ist.
DharmaTurtle

20

Sehr sauber und MVVM Weg ist zu verwenden InteractionTriggerund zu CallMethodActiondefinierenMicrosoft.Interactivity.Core

Sie müssen wie unten einen neuen Namespace hinzufügen

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

Sie benötigen die Assembly Microsoft.Xmal.Behaviours.Wpf, und dann funktioniert der folgende xaml-Code.

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

Sie brauchen keinen Code dahinter oder irgendetwas anderes und können auch jede andere Methode von aufrufen Window.


Dies ist der sauberste Ansatz, den ich bisher gesehen habe, da kein Code dahinter steckt und das ViewModel nicht an die Ansicht gekoppelt ist. Es funktioniert auch mit Befehlen. Sie müssen einige zusätzliche DLLs bereitstellen und benötigen zusätzliche Arbeit, wenn Sie das Schließen innerhalb Ihres Befehls abbrechen möchten. Es ist nicht viel anders, als ein Click-Ereignis im Code dahinter zu haben und nur Close () aufzurufen. Der Code hinter dem Ereignishandler würde es einfacher machen, das Szenario zu handhaben, in dem der Befehl close das Ereignis close abbricht (z. B. wenn ein Fehler gespeichert wurde Daten). Vielen Dank Massimiliano
Richard Moore

1
Rajnikants Code funktioniert in VS 2019 länger, da MS WPF-Verhaltensweisen Open Source gemacht und in das NuGet-Paket Microsoft.Xaml.Behaviors.Wpf verschoben hat. Informationsquelle sind Kommentare: Developercommunity.visualstudio.com/content/problem/198075/… . Detaillierte Schritte zum Umgestalten Ihres Codes finden Sie unter: devblogs.microsoft.com/dotnet/…
Eric Wood

Es ist sauberer, aber ich glaube, es sollte vom Ansichtsmodell gesteuert werden, nicht von der Ansicht, da es zu einem Befehl gehört, nicht zur Standardschaltfläche zum Schließen.
Daniel Möller

17

Wie jemand kommentierte, ist der Code, den ich gepostet habe, nicht MVVM-freundlich. Wie wäre es mit der zweiten Lösung?

1. keine MVVM-Lösung (ich werde dies nicht als Referenz löschen)

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. wahrscheinlich bessere Lösung: Verwenden von angehängten Verhaltensweisen

XAML

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

Modell anzeigen

public ICommand OkCommand
{
    get { return _okCommand; }
}

Verhaltensklasse Ähnliches:

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
Wiederholen Sie niemals mit mir das Fenster mit dem ViewModel. Schreiben Sie jetzt den Satz 100 Instanzen :)
Ignacio Soler Garcia

4
+1 IMHO ist dies die beste Lösung: Es macht die Sache, es ist die kürzeste, erfordert keine komplizierte Infrastruktur, löst das Problem auf MVVM-Weise. @ SoMoS - hier gibt es keine Kopplung. Überhaupt. VM ist sich der Existenz von View nicht bewusst. Der Befehl ruft Window als Parameter ab, da er wissen muss, was geschlossen werden soll.
Ilia Barahovski

2
+1 @SoMoS Ich stimme Ilia zu, dies ist genau die entkoppelte Lösung. Ich würde die Logik zum Speichern und Schließen von Fenstern nicht miteinander kombinieren, aber dies ist eine andere Sache
makc

8
@ Barahovski: Fenster ist ein WPF-Objekt. Das Ansichtsmodell sollte nicht von einem WPF oder einem schweren Framework abhängen. Wie kann ein Komponententest (ohne Benutzeroberfläche) eine Windows-Instanz dazu bringen, dies zu testen?
g.pickardou

@IgnacioSolerGarcia +1 zu Ihrem Kommentar. Vielleicht ist ein angehängter Verhaltensansatz eine bessere Lösung? Ich habe meine Antwort damit aktualisiert.
Simone

13

Ich persönlich würde ein Verhalten verwenden, um so etwas zu tun:

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

Sie können dies dann an Ihre anhängen Windowund Buttondie Arbeit erledigen:

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

Ich habe hinzugefügt Commandund CommandParameterhier, damit Sie einen Befehl ausführen können, bevor das Windowschließt.


1
Ich bin etwas spät dran zu dieser Party, aber dies kann weiter vereinfacht werden, indem das Verhalten direkt auf den Button gesetzt wird. Sie können einen Handler für das Click-Ereignis definieren, der aufruft Window.GetWindow(AssociatedObject)?.Close()(natürlich mit entsprechenden Nullprüfungen), der in Überschreibungen für die OnAttachedund OnDetaching-Hooks angehängt / getrennt wird . Drei triviale Funktionen, Null-Eigenschaften und können an eine beliebige Anzahl von Schaltflächen in demselben (oder verschiedenen) Fenstern angehängt werden.
BionicOnion

Hm, wäre das nicht ein besseres Design, wenn Sie, anstatt das Verhalten an das Fenster anzuhängen und eine Schaltfläche zu übergeben, das Verhalten an die Schaltfläche anhängen und das Fenster übergeben könnten ?
Sören Kuklau

9

Für kleine Apps verwende ich meinen eigenen Application Controller zum Anzeigen, Schließen und Entsorgen von Fenstern und DataContexts. Es ist ein zentraler Punkt in der Benutzeroberfläche einer Anwendung.

Es ist ungefähr so:

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

und ihre Aufrufe von ViewModels :

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

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

Natürlich können Sie einige Einschränkungen in meiner Lösung finden. Nochmals: Ich benutze es für kleine Projekte und es ist genug. Wenn Sie interessiert sind, kann ich den vollständigen Code hier oder woanders posten.


7

Ich habe versucht, dieses Problem auf generische MVVM-Weise zu beheben, aber ich stelle immer fest, dass ich am Ende unnötig komplexe Logik habe. Um ein enges Verhalten zu erreichen, habe ich eine Ausnahme von der Regel gemacht, dass kein Code dahinter steht, und einfach gute alte Ereignisse im Code dahinter verwendet:

XAML:

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

Code dahinter:

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

Obwohl ich mir wünsche, dass dies mit Befehlen / MVVM besser unterstützt wird, denke ich einfach, dass es keine einfachere und klarere Lösung als die Verwendung von Ereignissen gibt.


6

Ich verwende das Publish Subscribe-Muster für komplizierte Klassenabhängigkeiten:

ViewModel:

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

Fenster:

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

    public SomeWindow()
    {
        InitializeComponent();

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

Sie können Bizmonger.Patterns nutzen , um den 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
}

}}

Abonnement

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

Für diese Aufgabe gibt es ein nützliches Verhalten, das MVVM nicht unterbricht. Dieses Verhalten wurde mit Expression Blend 3 eingeführt, damit sich die Ansicht in Befehle einbinden kann, die vollständig im ViewModel definiert sind.

Dieses Verhalten zeigt eine einfache Technik, mit der das ViewModel die Abschlussereignisse der Ansicht in einer Model-View-ViewModel-Anwendung verwalten kann.

Auf diese Weise können Sie ein Verhalten in Ihrer Ansicht (UserControl) verknüpfen, das die Steuerung des Fensters des Steuerelements ermöglicht, sodass das ViewModel steuern kann, ob das Fenster über Standard-ICommands geschlossen werden kann.

Verwenden von Verhalten, um dem ViewModel das Verwalten der View-Lebensdauer in MV-VM zu ermöglichen

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

Der obige Link wurde unter http://code.msdn.microsoft.com/Window-Close-Attached-fef26a66#content archiviert


4

Ich hatte einige Zeit mit diesem Thema zu kämpfen und entschied mich schließlich für den einfachsten Ansatz, der immer noch mit MVVM vereinbar ist: Lassen Sie die Schaltfläche den Befehl ausführen, der das ganze schwere Heben ausführt, und lassen Sie den Click-Handler der Schaltfläche das Fenster schließen.

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!

Es stimmt, es gibt immer noch Code-Behind, aber daran ist nichts von Natur aus schlecht. Und aus OO-Sicht ist es für mich am sinnvollsten, dem Fenster nur zu sagen, dass es sich selbst schließen soll.


4

Wir haben die Eigenschaft name in der .xaml-Definition:

x:Name="WindowsForm"

Dann haben wir den Knopf:

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

Dann im ViewModel:

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

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

Dann endlich die Aktionsmethode:

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

}

Ich habe diesen Code verwendet, um ein Popup-Fenster aus einer Anwendung zu schließen.


2

Ich musste dies in einer WPF-Anwendung tun, die auf .Net Core 3.0 basiert, wo leider keine Verhaltensunterstützung im Microsoft.Xaml.Behaviors.Wpf NuGet-Paket offiziell verfügbar war .

Stattdessen habe ich mich für eine Lösung entschieden, bei der das Fassadenmuster verwendet wurde.

Schnittstelle:

public interface IWindowFacade
{
    void Close();
}

Fenster:

public partial class MainWindow : Window, IWindowFacade

Standardbefehlseigenschaft für das Ansichtsmodell:

public ICommand ExitCommand
…

Kontrollbindung:

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

Befehl:

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

Da die Close()Methode bereits von der WindowKlasse implementiert wurde , ist das Anwenden der Fassadenschnittstelle auf das Fenster der einzige erforderliche Code in der UI-Ebene (für dieses einfache Beispiel). Der Befehl in der Präsentationsebene vermeidet Abhängigkeiten von der Ansichts- / Benutzeroberflächenebene, da er keine Ahnung hat, womit er spricht, wenn er die CloseMethode an der Fassade aufruft .


2

xaml.csRufen Sie in Ihrer aktuellen Fensterdatei den folgenden Code auf:

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

Das sollte das Ding machen.
Es hat bei mir funktioniert, ich hoffe, dass es Ihnen auch so geht.


1

Ich habe folgende Lösung in Silverlight. Wäre auch in 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>

Ich wünsche euch allen gute Ideen und Projekte ;-)



1

Ich denke, der einfachste Weg wurde noch nicht (fast) aufgenommen. Verwenden Sie anstelle von Behaviors, das neue Abhängigkeiten hinzufügt, einfach angehängte Eigenschaften:

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

Dann stellen Sie es einfach auf Ihren Dialogschaltflächen ein:

<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

Ich musste mich auch mit diesem Problem befassen, also hier meine Lösung. Es funktioniert großartig für mich.

1. Erstellen Sie die Klasse 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. Definieren Sie Ihren Befehl

        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. Binden Sie Ihren Befehl in die Ansicht

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

4. Und jetzt das Fenster

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

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

Damit können Sie den Befehl auch in der xaml-Datei binden, das Fenster mit FindAncestor suchen und an den Befehlsparameter binden.


0

Ich habe nach einer Lösung für das gleiche Problem gesucht und festgestellt, dass das Folgende gut funktioniert. Die Lösung ähnelt der von OP in seiner Frage erwähnten mit einigen Unterschieden:

  1. Keine Notwendigkeit von IsCancelEigentum.

  2. Der Code dahinter sollte das Fenster nicht schließen. Einfach einstellenDialogResult

In meinem Fall wird zuerst der Code dahinter ausgeführt und dann der an die Schaltfläche gebundene Modellbefehl angezeigt.

XAML

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

Code dahinter

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

Modell anzeigen

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

Hoffe das hilft.


0

Sie könnten die Frage umformulieren und auf diese Weise eine andere Lösung finden. Wie kann ich die Kommunikation zwischen Ansichten, Ansichtsmodellen und so weiter in einer MVVM-Umgebung aktivieren? Sie können das Mediator-Muster verwenden. Es ist im Grunde ein Benachrichtigungssystem. Für die eigentliche Mediator-Implementierung googeln Sie danach oder fragen Sie mich, und ich kann es per E-Mail senden.

Erstellen Sie einen Befehl, mit dem die Ansicht geschlossen werden soll.

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

Der Mediator wird eine Benachrichtigung auslösen (ein Token)

Hören Sie sich diese Benachrichtigung (Token) wie folgt im View codebehind-Konstruktor an:

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

0

Die Lösung zum Schließen eines Fensters in wpf, die für mich funktioniert hat, wird hier nicht beantwortet, daher dachte ich, ich würde auch meine Lösung hinzufügen.

        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.

            //            }
        }

Fügen Sie der Schaltfläche in Ihrem Fenster das CloseWindow-Ereignis wie folgt hinzu.

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

Sie möchten nicht unhöflich sein, aber wenn Sie das Click-Ereignis im Code hinter dem Fenster selbst behandelt haben, können Sie das Fenster direkt schließen, indem Sie Close () aufrufen. Sie müssen nicht nach dem übergeordneten Fenster der Schaltfläche suchen, um sie schließen zu können. Ihre Antwort hat nichts mit MVVM zu tun und Ihr Code kann wie folgt fortgesetzt werden: private void CloseWindow (Objektabsender, RoutedEventArgs e) {Close (); }
Alexandru Dicu

0

Ein einfacher Ansatz ist das Schließen des Fensters bei der Implementierung von saveComand. Verwenden Sie den folgenden Code, um das Fenster zu schließen.

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

Das untergeordnete Fenster wird geschlossen.


-2

Sie können es ohne Code dahinter tun. Erstellen Sie einen Befehl, und rufen Sie in Execute method die Methode "Save" im Ansichtsmodell und anschließend die Methode close im Bearbeitungsfenster auf, die Sie über den Parameter an den Befehl übergeben können:

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

Schaltfläche Speichern & Schließen XAML:

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

Code dahinter wäre besser, als die Instanz der Ansicht im Ansichtsmodell wie folgt zu haben. Bei der Frage geht es darum, dem MVVM-Muster zu folgen.
Alexandru Dicu
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.