wake-up-neo.com

Wie soll das ViewModel das Formular schließen?

Ich versuche, WPF und das MVVM-Problem zu lernen, habe aber einen Haken gefunden .. Diese Frage ist ähnlich, aber nicht ganz die gleiche wie diese (Handling-Dialoge-in-Wpf-with-mvvm) ...

Ich habe ein "Login" -Formular, das mit dem MVVM-Muster geschrieben wurde.

Dieses Formular verfügt über ein ViewModel, das den Benutzernamen und das Kennwort enthält, die über normale Datenbindungen an die Ansicht in der XAML gebunden sind. Außerdem enthält es den Befehl "Login", der an die Schaltfläche "Login" im Formular gebunden ist. mit normaler Datenbindung.

Wenn der Befehl "Login" ausgelöst wird, ruft er eine Funktion im ViewModel auf, die ausgeht und Daten über das Netzwerk sendet, um sich anzumelden. Wenn diese Funktion abgeschlossen ist, gibt es zwei Aktionen:

  1. Das Login war ungültig - wir zeigen nur eine MessageBox und alles ist in Ordnung

  2. Das Login war gültig, wir müssen das Login-Formular schließen und es als DialogResult ... wahr zurückgeben.

Das Problem ist, das ViewModel weiß nichts über die tatsächliche Ansicht. Wie kann es also die Ansicht schließen und sagen, dass sie ein bestimmtes DialogResult zurückgeben soll? Ich könnte etwas Code in das CodeBehind stecken und/oder die View an das ViewModel weitergeben, aber das scheint den ganzen Punkt von MVVM völlig zu besiegen ...


Aktualisieren

Am Ende habe ich gerade die "Reinheit" des MVVM-Musters verletzt und die View ein Closed -Ereignis veröffentlichen und eine Close-Methode verfügbar machen. Das ViewModel würde dann einfach view.Close aufrufen. Die Ansicht ist nur über eine Schnittstelle bekannt und über einen IOC -Container verdrahtet, sodass keine Testbarkeit oder Wartbarkeit verloren geht.

Es scheint ziemlich dumm zu sein, dass die akzeptierte Antwort bei -5 Stimmen liegt! Ich bin mir zwar der guten Gefühle bewusst, die man hat, wenn man ein Problem löst, während man "rein" ist. Sicherlich bin ich nicht der einzige, der denkt, dass 200 Zeilen von Ereignissen, Befehlen und Verhaltensweisen nur um eine einzeilige Methode zu vermeiden Der Name von "Mustern" und "Reinheit" ist ein bisschen lächerlich ....

231
Orion Edwards

Thejuans Antwort inspirierte mich dazu, eine einfachere beigefügte Eigenschaft zu schreiben. Keine Styles, keine Auslöser; Stattdessen können Sie einfach Folgendes tun:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

Das ist fast so sauber, als hätte das WPF-Team alles richtig gemacht und DialogResult überhaupt zu einer Abhängigkeitseigenschaft gemacht. Fügen Sie einfach eine bool? DialogResult-Eigenschaft in Ihr ViewModel ein und implementieren Sie INotifyPropertyChanged. Wenn Sie ViewModel schließen, kann das ViewModel das Fenster schließen (und dessen DialogResult festlegen), indem Sie einfach eine Eigenschaft festlegen. MVVM wie es sein sollte.

Hier ist der Code für DialogCloser:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

Ich habe auch dieses in meinem Blog gepostet.

308
Joe White

Aus meiner Sicht ist die Frage ziemlich gut, da der gleiche Ansatz nicht nur für das "Login" -Fenster verwendet werden würde, sondern für jedes Fenster. Ich habe viele Vorschläge geprüft und keiner ist für mich in Ordnung. Bitte überprüfen Sie meinen Vorschlag aus dem MVVM Design Pattern Artikel .

Jede ViewModel-Klasse sollte von WorkspaceViewModel erben, das über das Ereignis RequestClose und die CloseCommand-Eigenschaft des Typs ICommand verfügt. Die Standardimplementierung der CloseCommand-Eigenschaft löst das RequestClose-Ereignis aus.

Um das Fenster zu schließen, sollte die OnLoaded-Methode Ihres Fensters überschrieben werden:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

oder OnStartup Methode Ihrer App:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

Ich denke, dass die Implementierung von RequestClose event und CloseCommand in WorkspaceViewModel ziemlich klar ist, aber ich werde zeigen, dass sie konsistent sind:

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

Und der Quellcode der RelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

P.S. Behandle mich nicht schlecht für diese Quellen! Wenn ich sie gestern hätte, hätte mir das ein paar Stunden erspart ...

P.P.S. Alle Kommentare oder Vorschläge sind willkommen.

65
Budda

Ich habe angefügte Verhalten verwendet, um das Fenster zu schließen . Binden Sie eine "Signal" -Eigenschaft in Ihrem ViewModel an das angefügte Verhalten (ich benutze tatsächlich einen Auslöser) Wenn es auf true gesetzt ist, schließt das Verhalten das Fenster.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

18
Adam Mills

Es gibt viele Kommentare, die die Vor- und Nachteile von MVVM hier diskutieren. Für mich stimme ich mit Nir überein; Es ist eine Frage der richtigen Verwendung des Musters und MVVM passt nicht immer. Die Menschen scheinen bereit gewesen zu sein, alle wichtigen Prinzipien des Softwaredesigns zu opfern, nur um MVVM anzupassen.

Das heißt, ich denke, Ihr Fall könnte gut mit etwas Refactoring zusammenpassen.

In den meisten Fällen, die mir begegnet sind, können Sie mit WPF OHNE mehrere Windows umgehen. Vielleicht könnten Sie Frames und Pages anstelle von Windows mit DialogResults verwenden.

In Ihrem Fall würde mein Vorschlag LoginFormViewModel die LoginCommand behandeln, und wenn der Login ungültig ist, setzen Sie eine Eigenschaft für LoginFormViewModel auf einen geeigneten Wert (false oder einen Aufzählungswert wie UserAuthenticationStates.FailedAuthentication). Sie würden dasselbe für eine erfolgreiche Anmeldung tun (true oder einen anderen Aufzählungswert). Sie würden dann eine DataTrigger verwenden, die auf die verschiedenen Benutzerauthentifizierungszustände reagiert und eine einfache Setter verwenden könnte, um die Source-Eigenschaft der Frame zu ändern. 

Wenn Ihr Anmeldefenster eine DialogResult zurückgibt, denke ich, werden Sie verwirrt; dass DialogResult wirklich eine Eigenschaft Ihres ViewModels ist. In meiner, allerdings begrenzten Erfahrung mit WPF, wenn etwas sich nicht richtig anfühlt, geschieht es normalerweise, weil ich darüber nachdenke, wie ich in WinForms dasselbe getan hätte. 

Hoffentlich hilft das.

15
Stimul8d

Wenn Sie davon ausgehen, dass Ihr Anmeldedialogfeld das erste Fenster ist, das erstellt wird, versuchen Sie Folgendes in Ihrer LoginViewModel-Klasse:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }
9
Jim Wallace

Wie ich damit umgehen würde, ist das Hinzufügen eines Event-Handlers in mein ViewModel. Wenn der Benutzer erfolgreich angemeldet war, würde ich das Ereignis auslösen. In meiner Ansicht würde ich an dieses Ereignis anschließen und wenn es ausgelöst wurde, würde ich das Fenster schließen. 

6
Billy Jacobs

Dies ist eine einfache und saubere Lösung - Sie fügen dem ViewModel ein Ereignis hinzu und weisen das Fenster an, sich selbst zu schließen, wenn dieses Ereignis ausgelöst wird.

Weitere Informationen finden Sie in meinem Blogbeitrag Fenster in ViewModel schließen.

5
Shimmy

Hier ist, was ich anfangs gemacht habe, was funktioniert, aber es scheint ziemlich langatmig und hässlich zu sein (globales statisches ist alles nie gut)

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

Später entfernte ich dann den gesamten Code und ließ nur die LoginFormViewModel die Close-Methode in seiner Ansicht aufrufen. Es war viel schöner und leichter zu folgen. Der Sinn von Mustern besteht IMHO darin, den Menschen eine einfachere Möglichkeit zu bieten, die Funktionsweise Ihrer App zu verstehen. In diesem Fall war MVVM weitaus schwieriger zu verstehen, als wenn ich sie nicht verwendet hätte und jetzt ein anti-Muster.

4
Orion Edwards

Dies ist wahrscheinlich sehr spät, aber ich bin auf das gleiche Problem gestoßen und habe eine Lösung gefunden, die für mich funktioniert.

Ich kann nicht herausfinden, wie man eine App ohne Dialoge erstellt (vielleicht ist es nur ein Denkblock). Ich war also mit MVVM in einer Sackgasse und zeigte einen Dialog. So bin ich auf diesen CodeProject-Artikel gestoßen:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Welches ist ein UserControl, das im Grunde erlaubt, dass sich ein Fenster innerhalb des visuellen Baums eines anderen Fensters befindet (in xaml nicht zulässig). Außerdem wird eine boolesche DependencyProperty mit dem Namen IsShowing verfügbar gemacht.

Sie können einen Stil wie in einem Resourcedictionary festlegen, der grundsätzlich das Dialogfeld anzeigt, wenn die Content-Eigenschaft des Steuerelements! = Null über Trigger ausgelöst wird:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

In der Ansicht, in der Sie den Dialog anzeigen möchten, haben Sie einfach Folgendes:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

In ViewModel müssen Sie lediglich die Eigenschaft auf einen Wert setzen (Hinweis: Die ViewModel-Klasse muss INotifyPropertyChanged unterstützen, damit die Ansicht erkennt, dass etwas passiert ist).

wie so:

DialogViewModel = new DisplayViewModel();

Um das ViewModel mit der View abzugleichen, sollten Sie in einem Resourcedictionary so etwas haben:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

Bei all dem erhalten Sie einen einzeiligen Code, um den Dialog anzuzeigen. Das Problem, das Sie bekommen, ist, dass Sie den Dialog nicht einfach mit dem obigen Code schließen können. Aus diesem Grund müssen Sie ein Ereignis in eine ViewModel-Basisklasse eingeben, von der DisplayViewModel erbt, und anstelle des oben genannten Codes schreiben Sie dies

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

Dann können Sie das Ergebnis des Dialogs über den Rückruf bearbeiten.

Dies mag ein wenig komplex erscheinen, aber sobald die Grundlagen geschaffen sind, ist das ziemlich einfach. Dies ist wieder meine Implementierung, ich bin mir sicher, dass es noch andere gibt :)

Hoffe das hilft, es hat mich gerettet.

3
Jose

Ok, also ist diese Frage fast 6 Jahre alt und ich kann immer noch nicht finden, was ich denke, dass es die richtige Antwort ist, also erlaube mir, meine "2 Cent" zu teilen ...

Ich habe zwei Möglichkeiten, es zu tun, der erste ist der einfache ... der zweite am rechten, also wenn Sie nach dem richtigen suchen, überspringen Sie einfach # 1 und springen Sie zu # 2 :

1. Schnell und einfach (aber nicht vollständig)

Wenn ich nur ein kleines Projekt habe, erstelle ich manchmal einfach ein CloseWindowAction im ViewModel:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

Und wer auch immer die Ansicht erstellt oder im Code der Ansicht dahinter steht, ich setze nur die Methode ein, die die Aktion aufrufen wird:

(Denken Sie daran, dass es sich bei MVVM um die Trennung von View und ViewModel handelt ... das Code-Verhalten der View ist immer noch die View und solange es die richtige Trennung gibt, verletzen Sie das Muster nicht.)

Wenn ein ViewModel ein neues Fenster erstellt:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

Oder wenn Sie es in Ihrem Hauptfenster haben möchten, platzieren Sie es einfach unter dem Konstruktor Ihrer Ansicht:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

wenn Sie das Fenster schließen möchten, rufen Sie einfach die Aktion auf Ihrem ViewModel auf.


2. Der richtige Weg

Nun, die richtige Art und Weise, dies zu tun, ist Prism (IMHO), und alles darüber kann hier gefunden werden.

Sie können einen Interaction Request erstellen, ihn mit den Daten füllen, die Sie in Ihrem neuen Fenster benötigen, zum Mittagessen hinzufügen, ihn schließen und sogar Daten empfangen. All dies gekapselt und MVVM genehmigt. Sie erhalten sogar erhalten einen Status darüber, wie das Fenster geschlossen wurde, als ob der Benutzer Canceled oder Accepted (OK-Schaltfläche) das Fenster und Daten zurück, falls Sie es benötigen. Es ist etwas komplizierter und Antwort 1, aber es ist viel vollständiger und ein Empfohlenes Muster von Microsoft.

Der Link, den ich gegeben habe, enthält alle Code-Ausschnitte und Beispiele. Ich werde mich also nicht darum kümmern, Code hier einzufügen. Lesen Sie einfach den Artikel zum Herunterladen des Prism-Schnellstarts und führen Sie ihn aus Damit es funktioniert, aber die Vorteile sind größer als das Schließen eines Fensters.

3
mFeinstein
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}
3
Amir Twito

Zu Ihrer Information, ich bin auf dieses Problem gestoßen, und ich denke, ich habe eine Arbeit gefunden, die keine Globals oder Statik erfordert, auch wenn dies nicht die beste Antwort ist. Ich lasse die Jungs euch selbst entscheiden.

In meinem Fall kennt das ViewModel, das das anzuzeigende Fenster instantiiert (nennen wir es ViewModelMain), auch das LoginFormViewModel (anhand der obigen Situation als Beispiel). 

Also habe ich auf dem LoginFormViewModel eine Eigenschaft erstellt, die vom Typ ICommand war (nennen wir es CloseWindowCommand). Vor dem Aufruf von .ShowDialog () im Fenster setze ich die CloseWindowCommand -Eigenschaft für LoginFormViewModel auf die window.Close () - Methode des Fensters, das ich instanziiert habe. In LoginFormViewModel muss ich nur CloseWindowCommand.Execute () aufrufen, um das Fenster zu schließen.

Ich nehme an, es ist eine Art Workaround/Hack, aber es funktioniert gut, ohne das MVVM-Pattern wirklich zu unterbrechen.

Fühlen Sie sich frei, diesen Prozess so zu kritisieren, wie Sie möchten, ich kann ihn annehmen! :)

3
Chris Walker

Das ViewModel kann ein Ereignis anzeigen, für das sich die View registriert. Wenn das ViewModel entscheidet, wann die Ansicht geschlossen werden soll, wird dieses Ereignis ausgelöst, wodurch die Ansicht geschlossen wird. Wenn Sie möchten, dass ein bestimmter Ergebniswert zurückgegeben wird, haben Sie dafür eine Eigenschaft im ViewModel.

2

Ich möchte nur zu der großen Anzahl von Antworten hinzufügen, ich möchte Folgendes hinzufügen. Wenn Sie davon ausgehen, dass sich in Ihrem ViewModel ein ICommand befindet und Sie möchten, dass der Befehl das Fenster (oder eine andere Aktion für diese Angelegenheit) schließt, können Sie Folgendes verwenden.

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

Es ist nicht perfekt und kann schwierig zu testen sein (da es schwierig ist, statische Daten zu simulieren), aber es ist sauberer (IMHO) als die anderen Lösungen.

Erick

0
Erick T

Eine andere Lösung besteht darin, eine Eigenschaft mit INotifyPropertyChanged in View Model wie DialogResult zu erstellen.

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

Das wichtigste Fragment ist _someViewModel_PropertyChanged.DialogResultPropertyName kann eine öffentliche Konstante in SomeViewModel sein.

Ich verwende diesen Trick, um einige Änderungen in View Controls vorzunehmen, falls dies in ViewModel schwierig ist. OnPropertyChanged in ViewModel können Sie in View alles tun, was Sie möchten. ViewModel ist immer noch "Unit-Testable" und einige kleine Codezeilen dahinter machen keinen Unterschied.

0
sliwinski.lukas

Ich habe alle Antworten gelesen, aber ich muss sagen, die meisten von ihnen sind einfach nicht gut genug oder noch schlimmer.

Sie könnten dies mit der DialogService -Klasse, die für die Anzeige des Dialogfensters und das Zurückgeben des Dialogergebnisses verantwortlich ist, auf schöne Weise handhaben. Ich habe ein Beispielprojekt , das seine Implementierung und Verwendung demonstriert.

hier sind die wichtigsten Teile:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

Ist das nicht einfacher? straffer, lesbarer und nicht zuletzt einfacher zu debuggen als EventAggregator oder andere ähnliche Lösungen?

wie Sie sehen können, habe ich in meinen Ansichtsmodellen den ersten Ansatz von ViewModel verwendet, der in meinem Beitrag hier beschrieben wird: Best Practice für das Aufrufen von View aus ViewModel in WPF

Natürlich muss der Code DialogService.ShowDialog mehr Optionen zum Konfigurieren des Dialogs haben, z. Tasten und Befehle sollten sie ausführen. Es gibt verschiedene Möglichkeiten, dies zu tun, aber es liegt außerhalb des Geltungsbereichs :)

0
Liero

Erstellen Sie einenDependency Propertyin IhrerView/ beliebigen UserControl (oder Window), die Sie schließen möchten. Wie unten:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

Und binden Sie es von Ihrer ViewModel-Eigenschaft :

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Eigenschaft inVeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Lösen Sie nun den Schließvorgang aus, indem Sie den WertCloseWindowin ViewModel ändern. :)

0
Kylo Ren

Dies beantwortet zwar nicht die Frage, wie dies über das Viewmodel erfolgen soll, es zeigt jedoch, wie dies nur mit XAML + dem Blend-SDK möglich ist.

Ich habe mich entschieden, zwei Dateien aus dem Blend-SDK herunterzuladen und zu verwenden, die Sie als Paket von Microsoft über NuGet verwenden können. Die Dateien sind:

System.Windows.Interactivity.dll und Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll bietet Ihnen Nice-Funktionen, z. B. das Festlegen von Eigenschaften oder das Aufrufen einer Methode für Ihr Viewmodel oder ein anderes Ziel. Außerdem enthält es weitere Widgets.

Einige XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.Microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Wenn Sie sich nur für ein einfaches OK/Cancel-Verhalten entscheiden, können Sie die Eigenschaften IsDefault und IsCancel verwenden, solange das Fenster mit Window.ShowDialog () angezeigt wird.
Ich hatte Probleme mit einer Schaltfläche, bei der die IsDefault-Eigenschaft auf true festgelegt war, die jedoch beim Laden der Seite ausgeblendet wurde. Es schien nicht gut zu spielen, nachdem es gezeigt wurde, also setze ich einfach die Window.DialogResult-Eigenschaft wie oben gezeigt und es funktioniert für mich.

0
Wes

Am Ende vermischte ich die Antwort von Joe White und etwas Code von die Antwort von Adam Mills , da ich ein Benutzersteuerelement in einem programmgesteuerten Fenster anzeigen musste. Der DialogCloser muss sich also nicht im Fenster befinden, er kann sich auf dem Benutzer selbst befinden

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

Und der DialogCloser findet das Fenster des Benutzersteuerelements, wenn es nicht an das Fenster selbst angehängt wurde.

namespace Wpf
{
  public static class DialogCloser
  {
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      var window = d.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}
0
Anuroopa Shenoy

Ich würde diesen Weg gehen:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}
0
romanoza

Verhalten ist hier der bequemste Weg. 

  • Von einer Hand kann es an das gegebene Viewmodel gebunden werden (das Signal "das Formular schließen!")

  • Von einer anderen Seite hat er Zugriff auf das Formular selbst, um notwendige formularbezogene Ereignisse zu abonnieren oder ein Bestätigungsdialogfeld oder etwas anderes anzuzeigen.

Das Schreiben des notwendigen Verhaltens kann schon beim ersten Mal langweilig sein. Von jetzt an können Sie es jedoch auf jedem einzelnen Formular, das Sie benötigen, durch genaues Einzeiler-XAML-Snippet wiederverwenden. Falls erforderlich, können Sie es als separate Assembly extrahieren, um es in jedes nächste gewünschte Projekt aufzunehmen.

0
Yury Schkatula

Warum nicht einfach das Fenster als Befehlsparameter übergeben?

C #:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />
0
chrislarson

Ich habe die Lösung von Joe White implementiert, habe jedoch Probleme mit gelegentlichen Fehlern "DialogResult" nur dann gesetzt, wenn Window erstellt und als Dialogfehler "angezeigt wurde.

Nach dem Schließen der View behielt ich das ViewModel bei und gelegentlich öffnete ich eine neue View mit derselben VM. Es hat den Anschein, dass das Schließen der neuen Ansicht vor dem Sammeln der alten Ansicht zur Folge hatte, dass DialogResultChanged versuchte, die DialogResult -Eigenschaft im geschlossenen Fenster zu setzen, wodurch der Fehler ausgelöst wurde.

Meine Lösung bestand darin, DialogResultChanged zu ändern, um die IsLoaded -Eigenschaft des Fensters zu überprüfen:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

Nach dieser Änderung werden alle Anhänge geschlossener Dialoge ignoriert.

0
Jim Hansen

Hier ist die einfache, fehlerfreie Lösung (mit Quellcode). Sie funktioniert für mich.

  1. Leiten Sie Ihr ViewModel von INotifyPropertyChanged ab.

  2. Erstellen Sie eine beobachtbare Eigenschaft CloseDialog in ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

  3. Fügen Sie in dieser Ansicht einen Handler für diese Eigenschaftsänderung hinzu

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. Jetzt bist du fast fertig. Im Event-Handler machen Sie DialogResult = true 

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    
0
Anil8753