Fenster aus ViewModel schließen


95

Ich erstelle ein Login mit a window control, damit sich ein Benutzer bei einer WPFAnwendung anmelden kann , die ich erstelle.

Bisher habe ich eine Methode erstellt, die prüft, ob der Benutzer die richtigen Anmeldeinformationen für die usernameund passwordin a textboxauf dem Anmeldebildschirm eingegeben hat , bindingzwei properties.

Ich habe dies erreicht, indem ich eine boolMethode wie diese geschaffen habe;

public bool CheckLogin()
{
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome " + user.Username + ", you have successfully logged in.");

        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
}

public ICommand ShowLoginCommand
{
    get
    {
        if (this.showLoginCommand == null)
        {
            this.showLoginCommand = new RelayCommand(this.LoginExecute, null);
        }
        return this.showLoginCommand;
    }
}

private void LoginExecute()
{
    this.CheckLogin();
} 

Ich habe auch eine command, die ich bindzu meinem Knopf innerhalb der xamlgleichen so;

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" />

Wenn ich den Benutzernamen und das Passwort eingebe, wird der entsprechende Code ausgeführt, unabhängig davon, ob er richtig oder falsch ist. Aber wie kann ich dieses Fenster im ViewModel schließen, wenn sowohl Benutzername als auch Passwort korrekt sind?

Ich habe zuvor versucht, ein zu verwenden, dialog modalaber es hat nicht ganz geklappt. Darüber hinaus habe ich in meiner app.xaml Folgendes ausgeführt: Die Anmeldeseite wird zuerst geladen, und sobald sie wahr ist, wird die eigentliche Anwendung geladen.

private void ApplicationStart(object sender, StartupEventArgs e)
{
    Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;

    var dialog = new UserView();

    if (dialog.ShowDialog() == true)
    {
        var mainWindow = new MainWindow();
        Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
        Current.MainWindow = mainWindow;
        mainWindow.Show();
    }
    else
    {
        MessageBox.Show("Unable to load application.", "Error", MessageBoxButton.OK);
        Current.Shutdown(-1);
    }
}

Frage: Wie kann ich den Login Window controlüber das ViewModel schließen?

Danke im Voraus.


Antworten:


147

Sie können das Fenster mit dem an Ihr ViewModel übergeben CommandParameter. Siehe mein Beispiel unten.

Ich habe eine CloseWindowMethode implementiert, die ein Windows als Parameter verwendet und es schließt. Das Fenster wird über an das ViewModel übergeben CommandParameter. Beachten Sie, dass Sie x:Namefür das Fenster ein Fenster definieren müssen, das geschlossen sein soll. In meinem XAML-Fenster rufe ich diese Methode über auf Commandund übergebe das Fenster selbst als Parameter mit an das ViewModel CommandParameter.

Command="{Binding CloseWindowCommand, Mode=OneWay}" 
CommandParameter="{Binding ElementName=TestWindow}"

ViewModel

public RelayCommand<Window> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
}

private void CloseWindow(Window window)
{
    if (window != null)
    {
       window.Close();
    }
}

Aussicht

<Window x:Class="ClientLibTestTool.ErrorView"
        x:Name="TestWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:localization="clr-namespace:ClientLibTestTool.ViewLanguages"
        DataContext="{Binding Main, Source={StaticResource Locator}}"
        Title="{x:Static localization:localization.HeaderErrorView}"
        Height="600" Width="800"
        ResizeMode="NoResize"
        WindowStartupLocation="CenterScreen">
    <Grid> 
        <Button Content="{x:Static localization:localization.ButtonClose}" 
                Height="30" 
                Width="100" 
                Margin="0,0,10,10" 
                IsCancel="True" 
                VerticalAlignment="Bottom" 
                HorizontalAlignment="Right" 
                Command="{Binding CloseWindowCommand, Mode=OneWay}" 
                CommandParameter="{Binding ElementName=TestWindow}"/>
    </Grid>
</Window>

Beachten Sie, dass ich das MVVM Light Framework verwende, aber das Prinzip gilt für jede wpf-Anwendung.

Diese Lösung verstößt gegen das MVVM-Muster, da das Ansichtsmodell nichts über die UI-Implementierung wissen sollte. Wenn Sie das MVVM-Programmierparadigma genau befolgen möchten, müssen Sie den Typ der Ansicht mit einer Schnittstelle abstrahieren.

MVVM- konforme Lösung (ehemals EDIT2)

Der Benutzer Crono erwähnt einen gültigen Punkt im Kommentarbereich:

Wenn Sie das Window-Objekt an das Ansichtsmodell übergeben, wird das MVVM-Muster meiner Meinung nach unterbrochen, da Ihre VM gezwungen ist, zu wissen, in was es angezeigt wird.

Sie können dies beheben, indem Sie eine Schnittstelle einführen, die eine Methode zum Schließen enthält.

Schnittstelle:

public interface ICloseable
{
    void Close();
}

Ihr überarbeitetes ViewModel sieht folgendermaßen aus:

ViewModel

public RelayCommand<ICloseable> CloseWindowCommand { get; private set; }

public MainViewModel()
{
    this.CloseWindowCommand = new RelayCommand<IClosable>(this.CloseWindow);
}

private void CloseWindow(ICloseable window)
{
    if (window != null)
    {
        window.Close();
    }
}

Sie müssen die ICloseableSchnittstelle in Ihrer Ansicht referenzieren und implementieren

Ansicht (Code dahinter)

public partial class MainWindow : Window, ICloseable
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

Antwort auf die ursprüngliche Frage: (früher EDIT1)

Ihre Anmeldeschaltfläche (CommandParameter hinzugefügt):

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding ShowLoginCommand}" CommandParameter="{Binding ElementName=LoginWindow}"/>

Dein Code:

 public RelayCommand<Window> CloseWindowCommand { get; private set; } // the <Window> is important for your solution!

 public MainViewModel() 
 {
     //initialize the CloseWindowCommand. Again, mind the <Window>
     //you don't have to do this in your constructor but it is good practice, thought
     this.CloseWindowCommand = new RelayCommand<Window>(this.CloseWindow);
 }

 public bool CheckLogin(Window loginWindow) //Added loginWindow Parameter
 {
    var user = context.Users.Where(i => i.Username == this.Username).SingleOrDefault();

    if (user == null)
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
    else if (this.Username == user.Username || this.Password.ToString() == user.Password)
    {
        MessageBox.Show("Welcome "+ user.Username + ", you have successfully logged in.");
        this.CloseWindow(loginWindow); //Added call to CloseWindow Method
        return true;
    }
    else
    {
        MessageBox.Show("Unable to Login, incorrect credentials.");
        return false;
    }
 }

 //Added CloseWindow Method
 private void CloseWindow(Window window)
 {
     if (window != null)
     {
         window.Close();
     }
 }

1
Danke für das Update @Joel. Eine letzte Frage, da die Methode einen Parameter von Window aufnimmt und wenn ich diese Methode in meinem Befehl aufrufe, erwartet sie einen Parameter, würde ich einen lokalen Window-Parameter erstellen, der für die Methode aufgerufen wird, z. private void LoginExecute(){this.CheckLogin();}<- CheckLogin muss einen Parameter aufnehmen.
WPFNoob

Entschuldigung, ich verstehe es nicht. Könnten Sie Ihre Frage ein wenig klären?
Joel

14
Wenn Sie Ihre Fenster nicht benennen möchten, können Sie den Parameter auch wie CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
folgt

33
Durch das Übergeben des WindowObjekts an das Ansichtsmodell wird das MVVM-Muster meiner Meinung nach unterbrochen, da Ihre VM gezwungen ist, zu wissen, in was es angezeigt wird. Was wäre, wenn die Ansicht stattdessen eine angedockte Registerkarte in einer MDI-Oberfläche wäre? Der richtige Weg, dies zu tun, besteht darin, eine Art IUIHost-Schnittstelle zu übergeben, die eine Close-Methode implementiert, und jede gewünschte Ansicht zu haben, um zu zeigen, wie Ihr VM sie implementiert.
Crono

2
Es ist in Ordnung, da die Schnittstelle die konkrete Implementierung für das ViewModel verbirgt. Das ViewModel weiß nichts über die Ansicht, außer dass es eine Close () -Methode implementiert. Die Ansicht kann also alles sein: ein WPF-Fenster, ein WinForms-Formular, eine UWP-Anwendung oder sogar ein WPF-Raster. Es entkoppelt die Ansicht vom Ansichtsmodell.
Joel

34

Wenn ich MVVM bleibe, denke ich, dass die Verwendung von Behaviors aus dem Blend SDK (System.Windows.Interactivity) oder einer benutzerdefinierten Interaktionsanforderung von Prism für diese Art von Situation sehr gut funktionieren könnte.

Wenn Sie den Verhaltensweg gehen, ist hier die allgemeine Idee:

public class CloseWindowBehavior : Behavior<Window>
{
    public bool CloseTrigger
    {
        get { return (bool)GetValue(CloseTriggerProperty); }
        set { SetValue(CloseTriggerProperty, value); }
    }

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

    private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = d as CloseWindowBehavior;

        if (behavior != null)
        {
            behavior.OnCloseTriggerChanged();
        }
    }

    private void OnCloseTriggerChanged()
    {
        // when closetrigger is true, close the window
        if (this.CloseTrigger)
        {
            this.AssociatedObject.Close();
        }
    }
}

Dann binden Sie in Ihrem Fenster den CloseTrigger einfach an einen booleschen Wert, der festgelegt wird, wenn das Fenster geschlossen werden soll.

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:local="clr-namespace:TestApp"
        Title="MainWindow" Height="350" Width="525">
    <i:Interaction.Behaviors>
        <local:CloseWindowBehavior CloseTrigger="{Binding CloseTrigger}" />
    </i:Interaction.Behaviors>

    <Grid>

    </Grid>
</Window>

Schließlich hätte Ihr DataContext / ViewModel eine Eigenschaft, die Sie festgelegt haben, als das Fenster wie folgt geschlossen werden soll:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private bool closeTrigger;

    /// <summary>
    /// Gets or Sets if the main window should be closed
    /// </summary>
    public bool CloseTrigger
    {
        get { return this.closeTrigger; }
        set
        {
            this.closeTrigger = value;
            RaisePropertyChanged(nameof(CloseTrigger));
        }
    }

    public MainWindowViewModel()
    {
        // just setting for example, close the window
        CloseTrigger = true;
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

(setze dein Window.DataContext = neues MainWindowViewModel ())


Vielen Dank für die Antwort @Steve, die Sie über das Binden des CloseTrigger an einen booleanWert erwähnt haben. Als du das gesagt hast, wolltest du, dass ich ein erschaffe DataTrigger, um es zu erreichen?
WPFNoob

Entschuldigung, ich hätte expliziter sein sollen - ich hätte eine Eigenschaft in meinem Ansichtsmodell (im obigen Beispiel eine mit dem Namen CloseTrigger), die auf true gesetzt würde, was letztendlich das Verhalten auslösen würde. Ich habe die Antwort aktualisiert
Steve Van Treeck

Das hat funktioniert, aber ich musste die Art und Weise ändern, wie meine Anwendung geladen wurde. Da ich für meine Hauptanwendung ein Fenster verwendet habe, wurden auch alle untergeordneten Fenster gelöscht. Vielen Dank.
WPFNoob

Das Setzen einer Eigenschaft auf true, um eine Aktion auszuführen, stinkt IMO.
Josh Noe

32

Normalerweise füge ich ein Ereignis in das Ansichtsmodell ein, wenn ich dies tun muss, und verbinde es dann mit dem, Window.Close()wenn das Ansichtsmodell an das Fenster gebunden wird

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

    private void Login()
    {
        // Login logic here
        OnRequestClose(this, new EventArgs());
    }
}

Und beim Erstellen des Anmeldefensters

var vm = new LoginViewModel();
var loginWindow = new LoginWindow
{
    DataContext = vm
};
vm.OnRequestClose += (s, e) => loginWindow.Close();

loginWindow.ShowDialog(); 

11
Anonymer Delegierter ist schnell geschrieben, aber es ist erwähnenswert, dass die Veranstaltung nicht abgemeldet werden kann (was ein Problem sein kann oder nicht). Normalerweise besser dran mit einem vollwertigen Event-Handler.
Mathieu Guindon

Das gefällt mir am besten. Es ist ohnehin schwer spezielle Verarbeitung zu vermeiden , wenn das Fenster angezeigt wird (zB Loaded, ContentRenderedfür Hauptfenster, Dialogdienste usw.) und fügt hinzu , ein bisschen , um es über Ansichtsmodell Ereignis ist ziemlich sauber , wie für mich. 3 Codezeilen benötigen keine Wiederverwendbarkeitslösung. PS: reines MVVM ist sowieso für Nerds.
Sinatr

Junge, das hat mir geholfen.
Dimitri

Dies ist weitaus besser als die akzeptierte Antwort, da das MVVM-Muster nicht beschädigt wird.
Spook

23

Es mag spät sein, aber hier ist meine Antwort

foreach (Window item in Application.Current.Windows)
{
    if (item.DataContext == this) item.Close();
}

1
Warum ist das nicht die eigentliche Antwort?
user2529011

1
@ user2529011 Einige würden sich zumindest beschweren, dass das Ansichtsmodell nichts über Application.Current.Windows wissen sollte
unterstützt Monica normalerweise

13

Nun, hier ist etwas, das ich in mehreren Projekten verwendet habe. Es mag wie ein Hack aussehen, aber es funktioniert gut.

public class AttachedProperties : DependencyObject //adds a bindable DialogResult to window
{
    public static readonly DependencyProperty DialogResultProperty = 
        DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(AttachedProperties), 
        new PropertyMetaData(default(bool?), OnDialogResultChanged));

    public bool? DialogResult
    {
        get { return (bool?)GetValue(DialogResultProperty); }
        set { SetValue(DialogResultProperty, value); }
    }

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var window = d as Window;
        if (window == null)
            return;

        window.DialogResult = (bool?)e.NewValue;
    }
}

Jetzt können Sie eine Bindung DialogResultan eine VM herstellen und deren Wert für eine Eigenschaft festlegen. Das Windowwird geschlossen, wenn der Wert eingestellt ist.

<!-- Assuming that the VM is bound to the DataContext and the bound VM has a property DialogResult -->
<Window someNs:AttachedProperties.DialogResult={Binding DialogResult} />

Dies ist eine Zusammenfassung dessen, was in unserer Produktionsumgebung läuft

<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:DialogControl="clr-namespace:AC.Frontend.Controls.DialogControl" 
        xmlns:hlp="clr-namespace:AC.Frontend.Helper"
        MinHeight="150" MinWidth="300" ResizeMode="NoResize" SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen" Title="{Binding Title}"
        hlp:AttachedProperties.DialogResult="{Binding DialogResult}" WindowStyle="ToolWindow" ShowInTaskbar="True"
        Language="{Binding UiCulture, Source={StaticResource Strings}}">
        <!-- A lot more stuff here -->
</Window>

Wie Sie sehen können, deklariere ich zuerst den Namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper"und anschließend die Bindung hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Das AttachedPropertysieht so aus. Es ist nicht dasselbe, was ich gestern gepostet habe, aber meiner Meinung nach sollte es keine Wirkung haben.

public class AttachedProperties
{
    #region DialogResult

    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));

    private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wnd = d as Window;
        if (wnd == null)
            return;

        wnd.DialogResult = (bool?) e.NewValue;
    }

    public static bool? GetDialogResult(DependencyObject dp)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        return (bool?)dp.GetValue(DialogResultProperty);
    }

    public static void SetDialogResult(DependencyObject dp, object value)
    {
        if (dp == null) throw new ArgumentNullException("dp");

        dp.SetValue(DialogResultProperty, value);
    }

    #endregion
}

Nein, das ist keine dumme Frage. Fügen Sie einfach die Deklaration der Bindung in das <Window />Element ein, wie ich es in meinem Ausschnitt dargestellt habe. Ich war einfach zu faul, um den Rest (Namespace-Deklarationen usw.) zu schreiben, der normalerweise auch dort deklariert wird.
DHN

1
Bitte beziehen Sie sich auf meine Bearbeitung. Ich habe Produktionscode gepostet, daher bin ich mir sicher, dass er funktioniert. Es sieht ein bisschen anders aus, aber der Code, den ich gestern gepostet habe, sollte auch funktionieren.
DHN

Danke, dass du das geklärt hast. Es stellte sich heraus, dass ich den falschen Namespace aufrief: S. Muss ich nur ein erstellen datatriggerund es der Schaltfläche zuweisen, damit es funktioniert? Nochmals Entschuldigung für die nooby Frage.
WPFNoob

Danke - nun, ich bin mir nur bewusst, dass ich zu viele Fragen stelle, die albern und dumm erscheinen und die Zeit der Leute verschwenden! Aber zurück zu meiner Frage. Wie schließe ich nach allem, was Sie erwähnt haben, das Fenster? Verwenden Sie eine DataTrigger¬ and setting value wahre?
WPFNoob

1
Nun, das ist der Teil, den ich dir überlasse. ; o) Denken Sie an die DataContextder Dialog. Ich würde erwarten, dass die VM set as DataContexteinen Befehl bereitstellt, der die Eigenschaft DialogResultoder was auch immer Sie an trueoder gebunden haben false, so dass die Dialoggeschlossen wird.
DHN

13

Einfacher Weg

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

In ViewModel implementieren

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

    public MyViewVM()
    {
        _viewId = Guid.NewGuid();
    }
}

Fügen Sie den allgemeinen Fenstermanager-Helfer hinzu

public static class WindowManager
{
    public static void CloseWindow(Guid id)
    {
        foreach (Window window in Application.Current.Windows)
        {
            var w_id = window.DataContext as IRequireViewIdentification;
            if (w_id != null && w_id.ViewID.Equals(id))
            {
                window.Close();
            }
        }
    }
}

Und schließen Sie es so im Ansichtsmodell

WindowManager.CloseWindow(ViewID);

Eine sehr schöne Lösung.
DonBoitnott

Ich habe den WindowManager ein wenig geändert, um das Dialogergebnis beim Schließen der öffentlichen statischen Leere von win festzulegen. CloseWindow (Guid-ID, bool dialogResult) {foreach (Fensterfenster in Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} nenne es wie folgt: WindowManager.CloseWindow (_viewId, true);
Lebhero

Gute Lösung, macht jedoch eine enge Kopplung zwischen Ansichtsmodell und WindowManager, die wiederum eng gekoppelt ist mit View(in Bezug auf PresentationFramework). Es wäre besser, wenn WindowManagerein Dienst über eine Schnittstelle an viewmodel übergeben würde. Dann können Sie Ihre Lösung einfach auf eine andere Plattform migrieren.
Spook

4

Hier ist ein einfaches Beispiel für die Verwendung des MVVM Light Messenger anstelle eines Ereignisses. Das Ansichtsmodell sendet eine Abschlussnachricht, wenn auf eine Schaltfläche geklickt wird:

    public MainViewModel()
    {
        QuitCommand = new RelayCommand(ExecuteQuitCommand);
    }

    public RelayCommand QuitCommand { get; private set; }

    private void ExecuteQuitCommand() 
    {
        Messenger.Default.Send<CloseMessage>(new CloseMessage());
    }

Dann wird es im Code hinter dem Fenster empfangen.

    public Main()
    {   
        InitializeComponent();
        Messenger.Default.Register<CloseMessage>(this, HandleCloseMessage);
    }

    private void HandleCloseMessage(CloseMessage closeMessage)
    {
        Close();
    }

Können Sie mir bitte mitteilen, wo ich die Implementierung von CloseMessage finden kann?
Roman O

CloseMessage ist nur eine leere Klasse, mit der der Typ der gesendeten Nachricht identifiziert wird. (Es könnte auch komplexe Nachrichteninfos enthalten, die hier nicht benötigt werden.)
IngoB

4

Wie wäre es damit ?

ViewModel:

class ViewModel
{
    public Action CloseAction { get; set; }
    private void Stuff()
    {
       // Do Stuff
       CloseAction(); // closes the window
    }
}

Verwenden Sie in Ihrem ViewModel CloseAction (), um das Fenster wie im obigen Beispiel zu schließen.

Aussicht:

public View()
{
    InitializeComponent();
    ViewModel vm = new ViewModel (); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if (vm.CloseAction == null)
        vm.CloseAction = new Action(() => this.Close());
}

3

Ich weiß, dass dies ein alter Beitrag ist, wahrscheinlich würde niemand so weit scrollen, ich weiß, dass ich es nicht getan habe. Nachdem ich stundenlang verschiedene Sachen ausprobiert hatte, fand ich diesen Blog und der Typ hat ihn getötet. Der einfachste Weg, dies zu tun, versuchte es und es funktioniert wie ein Zauber.

Blog

Im ViewModel:

...

public bool CanClose { get; set; }

private RelayCommand closeCommand;
public ICommand CloseCommand
{
    get
    {
        if(closeCommand == null)
        (
            closeCommand = new RelayCommand(param => Close(), param => CanClose);
        )
    }
}

public void Close()
{
    this.Close();
}

...

Fügen Sie dem ViewModel eine Action-Eigenschaft hinzu, definieren Sie sie jedoch aus der CodeBehind-Datei der View. Auf diese Weise können wir dynamisch eine Referenz im ViewModel definieren, die auf die Ansicht verweist.

Im ViewModel fügen wir einfach hinzu:

public Action CloseAction { get; set; }

Und in der Ansicht definieren wir es als solches:

public View()
{
    InitializeComponent() // this draws the View
    ViewModel vm = new ViewModel(); // this creates an instance of the ViewModel
    this.DataContext = vm; // this sets the newly created ViewModel as the DataContext for the View
    if ( vm.CloseAction == null )
        vm.CloseAction = new Action(() => this.Close());
}

Link ist unterbrochen: /
unterstützt Monica normalerweise

@gusmally bist du sicher? Ich öffnete es normal, versuchen Sie es erneut jkshay.com/…
Serlok

2

So können Sie im ViewModel einen neuen Event-Handler erstellen.

public event EventHandler RequestClose;

    protected void OnRequestClose()
    {
        if (RequestClose != null)
            RequestClose(this, EventArgs.Empty);
    }

Definieren Sie dann RelayCommand für ExitCommand.

private RelayCommand _CloseCommand;
    public ICommand CloseCommand
    {
        get
        {
            if(this._CloseCommand==null)
                this._CloseCommand=new RelayCommand(CloseClick);
            return this._CloseCommand;
        }
    }

    private void CloseClick(object obj)
    {
        OnRequestClose();
    }

Dann In XAML-Dateisatz

<Button Command="{Binding CloseCommand}" />

Legen Sie den DataContext in der Datei xaml.cs fest und abonnieren Sie das von uns erstellte Ereignis.

public partial class MainWindow : Window
{
    private ViewModel mainViewModel = null;
    public MainWindow()
    {
        InitializeComponent();
        mainViewModel = new ViewModel();
        this.DataContext = mainViewModel;
        mainViewModel.RequestClose += delegate(object sender, EventArgs args) { this.Close(); };
    }
}

Ich habe anstelle des Ereignisses einen MVVM Light Messenger verwendet.
Hamish Gunn

1

Mein angebotener Weg ist das Deklarieren eines Ereignisses in ViewModel und die Verwendung von Blend InvokeMethodAction wie unten.

Beispiel für ViewModel

public class MainWindowViewModel : BindableBase, ICloseable
{
    public DelegateCommand SomeCommand { get; private set; }
    #region ICloseable Implementation
    public event EventHandler CloseRequested;        

    public void RaiseCloseNotification()
    {
        var handler = CloseRequested;
        if (handler != null)
        {
            handler.Invoke(this, EventArgs.Empty);
        }
    }
    #endregion

    public MainWindowViewModel()
    {
        SomeCommand = new DelegateCommand(() =>
        {
            //when you decide to close window
            RaiseCloseNotification();
        });
    }
}

I Die schließbare Oberfläche ist wie folgt, muss jedoch nicht ausgeführt werden. ICloseable hilft beim Erstellen eines generischen Ansichtsdienstes. Wenn Sie also Ansicht und ViewModel durch Abhängigkeitsinjektion erstellen, können Sie Folgendes tun

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Verwendung von ICloseable

var viewModel = new MainWindowViewModel();
        // As service is generic and don't know whether it can request close event
        var window = new Window() { Content = new MainView() };
        var closeable = viewModel as ICloseable;
        if (closeable != null)
        {
            closeable.CloseRequested += (s, e) => window.Close();
        }

Und unten ist Xaml. Sie können diese Xaml auch dann verwenden, wenn Sie keine Schnittstelle implementieren. Sie benötigen lediglich Ihr Ansichtsmodell, um CloseRquested auszulösen.

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFRx"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
xmlns:ViewModels="clr-namespace:WPFRx.ViewModels" x:Name="window" x:Class="WPFRx.MainWindow"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" 
d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}}">

<i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding Mode=OneWay}" EventName="CloseRequested" >
        <ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

<Grid>
    <Button Content="Some Content" Command="{Binding SomeCommand}" Width="100" Height="25"/>
</Grid>


1

Sie können das MessengerMVVMLight-Toolkit verwenden. ViewModelWenn Sie eine Nachricht wie folgt senden , registrieren Sie sich
Messenger.Default.Send(new NotificationMessage("Close"));
in Ihrem Windows-Code hinter InitializeComponentdieser Nachricht wie folgt:

Messenger.Default.Register<NotificationMessage>(this, m=>{
    if(m.Notification == "Close") 
    {
        this.Close();
    }
   });

Weitere Informationen zum MVVMLight-Toolkit finden Sie hier: MVVMLight-Toolkit in Codeplex

Beachten Sie, dass es in MVVM keine Regel "überhaupt kein Code-Behind" gibt und Sie sich für Nachrichten in einer Ansicht registrieren können, die Code-Behind enthält.


0

Es ist einfach. Sie können Ihre eigene ViewModel-Klasse für Login - LoginViewModel erstellen. Sie können view var dialog = new UserView () erstellen. in Ihrem LoginViewModel. Und Sie können Command LoginCommand in Schaltfläche einrichten.

<Button Name="btnLogin" IsDefault="True" Content="Login" Command="{Binding LoginCommand}" />

und

<Button Name="btnCancel" IsDefault="True" Content="Login" Command="{Binding CancelCommand}" />

ViewModel-Klasse:

public class LoginViewModel
{
    Window dialog;
    public bool ShowLogin()
    {
       dialog = new UserView();
       dialog.DataContext = this; // set up ViewModel into View
       if (dialog.ShowDialog() == true)
       {
         return true;
       }

       return false;
    }

    ICommand _loginCommand
    public ICommand LoginCommand
    {
        get
        {
            if (_loginCommand == null)
                _loginCommand = new RelayCommand(param => this.Login());

            return _loginCommand;
        }
    }

    public void CloseLoginView()
    {
            if (dialog != null)
          dialog.Close();
    }   

    public void Login()
    {
        if(CheckLogin()==true)
        {
            CloseLoginView();         
        }
        else
        {
          // write error message
        }
    }

    public bool CheckLogin()
    {
      // ... check login code
      return true;
    }
}

3
Ja, es ist auch eine gültige Lösung. Wenn Sie sich jedoch an MVVM und die Entkopplung von VMs und Ansichten halten möchten, werden Sie das Muster brechen.
DHN

Hallo @misak, nachdem Sie versucht haben, Ihre Lösung zu implementieren (wie die anderen Antworten), wird ein Object reference not set to an instance of an object.für die CloseLoginView-Methode ausgegeben. Irgendwelche Vorschläge, wie man dieses Problem löst?
WPFNoob

@WPFNoob - Ich stelle diese Lösung erneut ein. Beispiel funktioniert richtig. Möchten Sie eine vollständige Visual Studio-Lösung per E-Mail senden?
Misak

@ WPFNoob - Ich sehe das Problem. Sie erstellen eine Instanz als var dialog = new UserView ();. Clear keyword var (lokale Instanz) überschreibt globale Instanz in LoginViewModel
misak

0

So habe ich es ganz einfach gemacht:

YourWindow.xaml.cs

//In your constructor
public YourWindow()
{
    InitializeComponent();
    DataContext = new YourWindowViewModel(this);
}

YourWindowViewModel.cs

private YourWindow window;//so we can kill the window

//In your constructor
public YourWindowViewModel(YourWindow window)
{
    this.window = window;
}

//to close the window
public void CloseWindow()
{
    window.Close();
}

Ich sehe nichts falsches an der Antwort, die Sie gewählt haben. Ich dachte nur, dies könnte eine einfachere Möglichkeit sein, dies zu tun!


8
Dazu muss Ihr ViewModel Ihre Ansicht kennen und darauf verweisen.
AndrewS

@ Andrews warum ist das so schlimm?
Thestephenstanton

9
Um dem MVVM-Muster zu folgen, sollte das ViewModel nichts über die Ansicht wissen.
MetalMikester

1
Um dies zu erweitern, besteht der Sinn von MVVM darin, den größten Teil Ihrer GUI-Codeeinheit testbar zu machen. Ansichten weisen eine Vielzahl von Abhängigkeiten auf, die einen Unit-Test unmöglich machen. ViewModels sollten Unit-testbar sein, aber wenn Sie ihnen eine direkte Abhängigkeit von der Ansicht geben, werden sie es nicht sein.
ILMTitan

Um dies noch weiter auszubauen, können Sie mit ordnungsgemäß geschriebenem MVVM die Lösung einfach auf eine andere Plattform migrieren. Insbesondere sollten Sie in der Lage sein, Ihre Ansichtsmodelle ohne Änderungen wiederzuverwenden. In diesem Fall würde es nicht funktionieren, wenn Sie Ihre Lösung auf Android verschieben würden, da Android kein Konzept für ein Fenster hat. -1 für eine MVVM-brechende Lösung.
Spook

0

Sie können Fenster als Dienst behandeln (z. B. UI-Dienst) und sich über eine Schnittstelle an viewmodel übergeben , und zwar als solche:

public interface IMainWindowAccess
{
    void Close(bool result);
}

public class MainWindow : IMainWindowAccess
{
    // (...)
    public void Close(bool result)
    {
        DialogResult = result;
        Close();
    }
}

public class MainWindowViewModel
{
    private IMainWindowAccess access;

    public MainWindowViewModel(IMainWindowAccess access)
    {
        this.access = access;
    }

    public void DoClose()
    {
        access.Close(true);
    }
}

Diese Lösung hat die meisten Vorteile, wenn die Ansicht selbst an das Ansichtsmodell übergeben wird, ohne dass die MVVM beeinträchtigt wird, da die physische Ansicht zwar an das Ansichtsmodell übergeben wird, das letztere jedoch noch nichts über das erstere weiß, sondern nur einige davon sieht IMainWindowAccess. Wenn wir diese Lösung beispielsweise auf eine andere Plattform migrieren möchten, ist es nur eine Frage der IMainWindowAccessordnungsgemäßen Implementierung , zActivity .

Ich poste hier die Lösung, um einen anderen Ansatz als Ereignisse vorzuschlagen (obwohl er eigentlich sehr ähnlich ist), da die Implementierung etwas einfacher zu sein scheint als das Implementieren von Ereignissen (Anhängen / Trennen usw.), aber dennoch gut mit dem MVVM-Muster übereinstimmt.


-1

Sie können das aktuelle Fenster einfach mit dem folgenden Code schließen:

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

6
Wenn Sie mehr als ein Fenster haben, wird möglicherweise ein falsches Fenster geschlossen.
Sasha

17
Oh Gott! Sie haben MVVM
Hossein Shahdoost

-7

System.Environment.Exit (0); in Ansicht Modell würde funktionieren.


6
Nein, wird es nicht. Die Anwendung wird beendet und das aktuelle Fenster nicht geschlossen.
Tilak

Dies löste mein Problem, weil das Schließen des mainWindow (für mich) == das Beenden der Anwendung. Alle vorgeschlagenen Methoden außer dieser hatten schwierige Punkte, wenn sie von verschiedenen Threads aufgerufen wurden. aber diesem Ansatz ist es egal, wer der Anrufer-Thread ist :) das war alles, was ich brauchte!
Hamed

BuAHahahAHahahAha Entschuldigung konnte nicht widerstehen
L.Trabacchin
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.