webentwicklung-frage-antwort-db.com.de

Fenster aus ViewModel schließen

Ich möchte ein Login mit einem window control erstellen, um einem Benutzer das Anmelden bei einer WPF-Anwendung zu ermöglichen, die ich erstelle. 

Bisher habe ich eine Methode erstellt, die prüft, ob der Benutzer die korrekten Anmeldeinformationen für username und password in einer textbox im Anmeldebildschirm eingegeben hat, binding zwei properties

Dies ist mir durch die Erstellung einer bool-Methode gelungen.

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 bind zu meinem Button innerhalb der xaml mag, so wie ich;

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

Wenn ich den Benutzernamen und das Passwort eingebe, wird der entsprechende Code ausgeführt, und zwar entweder richtig oder falsch. Aber wie kann ich dieses Fenster aus dem ViewModel schließen, wenn sowohl Benutzername als auch Kennwort korrekt sind?

Ich habe es früher mit einem dialog modal versucht, aber es hat nicht ganz geklappt. Außerdem habe ich in meiner app.xaml so etwas wie das Folgende getan, das die Anmeldeseite zuerst lädt, dann einmal die wahre Anwendung, dann die eigentliche Anwendung.

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 vom ViewModel schließen?

Danke im Voraus.

66
WPFNoob

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

Ich habe eine CloseWindow-Methode implementiert, die einen Windows-Parameter als Parameter verwendet und ihn schließt. Das Fenster wird über CommandParameter an das ViewModel übergeben. Beachten Sie, dass Sie einen x:Name für das Fenster definieren müssen, das geschlossen werden soll. In meinem XAML-Fenster rufe ich diese Methode über Command auf und übergebe das Fenster selbst als Parameter mit CommandParameter an das ViewModel.

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, das Prinzip jedoch für jede Wpf-Anwendung gilt.

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

MVVM-konforme Lösung (Ehemalige EDIT2)

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

Durch das Übergeben des Window-Objekts an das Ansichtsmodell wird das MVVM-Muster zerstört IMHO, weil es Ihre vm zwingt zu wissen, in was es gesehen wird.

Sie können dies beheben, indem Sie eine Schnittstelle einführen, die eine close-Methode enthält. 

Schnittstelle:

public interface IClosable
{
    void Close();
}

Ihr refactored ViewModel sieht so aus:

ViewModel

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

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

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

Sie müssen die IClosable-Schnittstelle in Ihrer Ansicht referenzieren und implementieren

View (Code hinter)

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

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

Ihre Login-Schaltfläche (Added CommandParameter):

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

Wenn ich MVVM halte, denke ich, dass die Verwendung von Verhaltensweisen aus dem Blend-SDK (System.Windows.Interactivity) oder einer benutzerdefinierten Interaktionsanfrage von Prism für diese Art von Situation wirklich gut funktionieren könnte.

Wenn Sie die Verhaltensroute 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 würden Sie in Ihrem Fenster den CloseTrigger einfach an einen booleschen Wert binden, der beim Schließen des Fensters festgelegt würde.

<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 verfügt Ihr DataContext/ViewModel über eine Eigenschaft, die Sie beim Schließen des Fensters wie folgt festgelegt hätten:

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("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 = new MainWindowViewModel ())

26

Normalerweise füge ich ein Ereignis in das Ansichtsmodell ein, wenn ich dies tun muss, und binde es dann an die 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 Login-Fensters

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

loginWindow.ShowDialog(); 
24
ChrisO

Nun, hier habe ich etwas in mehreren Projekten verwendet. Es kann 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 DialogResult an eine VM binden und den Wert einer Eigenschaft festlegen. Window wird geschlossen, wenn der Wert festgelegt 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, deklariere ich zuerst den Namespace xmlns:hlp="clr-namespace:AC.Frontend.Helper" und danach die Bindung hlp:AttachedProperties.DialogResult="{Binding DialogResult}"

Das AttachedProperty sieht so aus. Es ist nicht dasselbe, was ich gestern gepostet habe, aber meiner Meinung nach sollte es keinen Effekt 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
}
12
DHN

Einfacher Weg

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Implementieren Sie in ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Fügen Sie einen allgemeinen Fenster-Manager 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ße es so im Viewmodel

WindowManager.CloseWindow(ViewID);
8
RassK

es kann spät sein, aber hier ist meine Antwort

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

Wie wäre es mit this ?

ViewModel:

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

Schließen Sie in Ihrem ViewModel das Fenster mit CloseAction () wie im obigen Beispiel.

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

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 mit der Antwort, die Sie gewählt haben, ich dachte nur, dass dies eine einfachere Möglichkeit wäre, dies zu tun!

3

Hier ist ein einfaches Beispiel, bei dem der MVVM Light Messenger anstelle eines Ereignisses verwendet wird. Das Ansichtsmodell sendet eine Schließmeldung, 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();
    }
2
Hamish Gunn

Sie können auf diese Weise einen neuen Ereignishandler im ViewModel 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 xaml.cs-Datei 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(); };
    }
}
1

Sie können Messenger aus dem MVVMLight-Toolkit verwenden. in deinem ViewModel sende eine Nachricht wie diese:
Messenger.Default.Send(new NotificationMessage("Close"));
Dann in Ihrem Windows-Code dahinter, nach InitializeComponent, registrieren Sie sich für diese 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 auf Codeplex

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

1
Amir Oveisi

Meine angebotene Methode ist Declare event in ViewModel und verwende die Mischung InvokeMethodAction wie folgt.

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

Die verschließbare Benutzeroberfläche ist wie folgt, muss jedoch nicht ausgeführt werden. ICloseable hilft beim Erstellen eines allgemeinen Ansichtsdienstes. Wenn Sie also View und ViewModel durch Abhängigkeitseingabe erstellen, können Sie Folgendes tun 

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Verwendung von ICsableable

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 dieses Xaml auch verwenden, wenn Sie keine Schnittstelle implementieren. Es wird nur Ihr Ansichtsmodell benötigt, um CloseRquested zu erzeugen. 

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

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

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

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

Blog

0
Serlok

Es ist ganz einfach .. Sie können Ihre eigene ViewModel-Klasse für Login erstellen - LoginViewModel . Sie können view var erstellen. in Ihrem LoginViewModel. Und Sie können Command LoginCommand in button 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;
    }
}
0
misak