Zamknij okno z ViewModel


96

Tworzę login przy użyciu a, window controlaby umożliwić użytkownikowi zalogowanie się do WPFaplikacji, którą tworzę.

Do tej pory stworzyłem metodę, która sprawdza, czy użytkownik podał poprawne dane uwierzytelniające dla usernameiw passworda textboxna ekranie logowania, bindingdwa properties.

Osiągnąłem to, tworząc taką boolmetodę;

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

Mam też, commandże mam binddo mojego przycisku w xamlpodobny sposób;

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

Kiedy wprowadzam nazwę użytkownika i hasło, wykonuje odpowiedni kod, niezależnie od tego, czy jest poprawny, czy zły. Ale jak mogę zamknąć to okno z ViewModel, jeśli zarówno nazwa użytkownika, jak i hasło są poprawne?

Wcześniej próbowałem użyć a, dialog modalale nie wyszło. Co więcej, w moim app.xaml wykonałem coś podobnego do poniższego, który najpierw ładuje stronę logowania, a następnie ładuje rzeczywistą aplikację.

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

Pytanie: Jak mogę zamknąć dane logowania Window controlz ViewModel?

Z góry dziękuję.


Odpowiedzi:


152

Możesz przekazać okno do swojego ViewModel przy użyciu CommandParameter. Zobacz mój przykład poniżej.

Zaimplementowałem CloseWindowmetodę, która przyjmuje parametr Windows jako parametr i zamyka go. Okno jest przekazywane do ViewModel za pośrednictwem CommandParameter. Zauważ, że musisz zdefiniować x:Namedla okna, które powinno być zamknięte. W moim oknie XAML wywołuję tę metodę via Commandi przekazuję samo okno jako parametr do ViewModel przy użyciu 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();
    }
}

Widok

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

Zauważ, że używam lekkiej struktury MVVM, ale zasada ma zastosowanie do każdej aplikacji wpf.

To rozwiązanie narusza wzorzec MVVM, ponieważ model widoku nie powinien wiedzieć nic o implementacji interfejsu użytkownika. Jeśli chcesz ściśle przestrzegać paradygmatu programowania MVVM, musisz wyodrębnić typ widoku za pomocą interfejsu.

Rozwiązanie zgodne z MVVM (dawna EDIT2)

użytkownik Crono wspomina ważny punkt w sekcji komentarzy:

Przekazanie obiektu Window do modelu widoku przerywa IMHO wzorca MVVM, ponieważ zmusza maszynę wirtualną do poznania, w czym jest wyświetlana.

Możesz to naprawić, wprowadzając interfejs zawierający metodę zamknięcia.

Berło:

public interface ICloseable
{
    void Close();
}

Twój refaktoryzowany ViewModel będzie wyglądał następująco:

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

Musisz odwołać się i zaimplementować ICloseableinterfejs w swoim widoku

Wyświetl (kod za)

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

Odpowiedź na pierwotne pytanie: (dawniej EDIT1)

Twój przycisk logowania (dodano parametr CommandParameter):

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

Twój kod:

 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
Dzięki za aktualizację @Joel. Ostatnie pytanie, ze względu na metodę przyjmującą parametr Window, a kiedy wywołuję tę metodę w ramach mojego polecenia, oczekuje parametru, czy utworzę lokalny parametr Window, który jest wywoływany dla metody, np.; private void LoginExecute(){this.CheckLogin();}<- CheckLogin musi przyjąć parametr.
WPFNoob

przepraszam, nie rozumiem, czy mógłbyś trochę wyjaśnić swoje pytanie?
Joel

14
Jeśli nie lubisz nazywać swoich okien, możesz również powiązać parametr w ten sposób:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
Przekazanie Windowobiektu do modelu widoku przerywa IMHO wzorca MVVM, ponieważ wymusza to na Twojej maszynie wirtualnej, aby wiedziała, w czym jest przeglądana. Co by było, gdyby widok był zamiast tego zadokowaną kartą w interfejsie MDI? Właściwym sposobem na zrobienie tego IMHO jest przekazanie pewnego rodzaju interfejsu IUIHost, który implementuje metodę Close i uzyskanie dowolnego widoku, który ma pokazywać maszynę wirtualną zaimplementowaną.
Crono,

2
Jest w porządku, ponieważ interfejs ukrywa konkretną implementację w ViewModel. ViewModel nie wie nic o widoku z wyjątkiem tego, że implementuje metodę Close (). Zatem widok może być wszystkim: oknem WPF, formularzem WinForms, aplikacją UWP lub nawet siatką WPF. Oddziela widok od modelu widoku.
Joel

35

Zwykle umieszczam zdarzenie w modelu widoku, gdy muszę to zrobić, a następnie podłączam je do Window.Close()podczas wiązania modelu widoku z oknem

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

I podczas tworzenia okna logowania

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

loginWindow.ShowDialog(); 

11
Anonimowy delegat jest szybko pisany, ale warto zauważyć, że wydarzenia nie można wyrejestrować (co może, ale nie musi, być problemem). Zwykle lepiej jest z pełnoprawnym programem obsługi zdarzeń.
Mathieu Guindon

1
To mi się najbardziej podoba. Jest w każdym razie trudno uniknąć specjalnego przetwarzania podczas wyświetlania okna (np Loaded, ContentRendereddo okna głównego, usługi dialogowych, etc.), dodając do niego trochę za pośrednictwem przypadku ViewModel jest dość czyste, jak dla mnie. 3 linie kodu tak naprawdę nie wymagają rozwiązania umożliwiającego ponowne użycie. PS: czysty MVVM i tak jest dla nerdów.
Sinatr

Chłopcze, to mi pomogło.
Dimitri

1
Jest to o wiele lepsze niż zaakceptowana odpowiedź, ponieważ nie łamie wzorca MVVM.
Spook

35

Pozostając MVVM, myślę, że użycie zachowań z Blend SDK (System.Windows.Interactivity) lub niestandardowego żądania interakcji z Prism może się naprawdę dobrze sprawdzić w tego rodzaju sytuacjach.

Jeśli wybierasz trasę Zachowanie, oto ogólna idea:

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

Następnie w swoim oknie możesz po prostu powiązać CloseTrigger z wartością logiczną, która zostanie ustawiona, gdy chcesz, aby okno zostało zamknięte.

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

Wreszcie, Twój DataContext / ViewModel miałby właściwość, którą ustawiłeś, gdy chcesz, aby okno zamykało się w następujący sposób:

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

(ustaw Window.DataContext = new MainWindowViewModel ())


Dzięki za odpowiedź @Steve, wspomniałeś o powiązaniu CloseTrigger z booleanwartością. Kiedy to powiedziałeś, czy chciałeś, żebym stworzył, DataTriggeraby to osiągnąć?
WPFNoob

Przepraszam, powinienem był być bardziej wyraźny - miałbym właściwość w moim modelu widoku (w powyższym przykładzie o nazwie CloseTrigger), która zostanie ustawiona na true, co w końcu wyzwoli zachowanie. Zaktualizowałem odpowiedź
Steve Van Treeck

To zadziałało, ale musiałem zmienić sposób ładowania mojej aplikacji. Ponieważ używałem Window dla mojej głównej aplikacji, zabił również wszystkie okna podrzędne. Dzięki.
WPFNoob

Ustawienie właściwości na true w celu wykonania akcji jest śmierdzącym IMO.
Josh Noe

22

może być późno, ale oto moja odpowiedź

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

1
dlaczego to nie jest właściwa odpowiedź?
user2529011

1
@ user2529011 niektórzy przynajmniej narzekaliby, że viewmodel nie powinien nic wiedzieć o Application.Current.Windows
gusmally wspiera Monikę

-1. Model widoku nie powinien w ogóle nic wiedzieć o widoku. Równie dobrze możesz po prostu napisać to w kodzie za tym sprawą.
Alejandro

13

Oto coś, czego użyłem w kilku projektach. Może wyglądać na włamanie, ale działa dobrze.

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

Teraz możesz powiązać DialogResultsię z maszyną wirtualną i ustawić jej wartość właściwości. WindowZostanie zamknięty, gdy wartość jest ustawiona.

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

To jest streszczenie tego, co dzieje się w naszym środowisku produkcyjnym

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

Jak widać, xmlns:hlp="clr-namespace:AC.Frontend.Helper"najpierw deklaruję przestrzeń nazw, a następnie powiązanie hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Do AttachedPropertywygląda następująco. To nie to samo, co opublikowałem wczoraj, ale IMHO to nie powinno mieć żadnego efektu.

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
}

Nie, to nie jest głupie pytanie. Po prostu umieść deklarację wiązania w <Window />elemencie, jak zilustrowałem w moim wyciętym. Byłem zbyt leniwy, żeby napisać resztę (deklaracje przestrzeni nazw itp.), Co zwykle jest tam również deklarowane.
DHN

1
Pls odnoszą się do mojej edycji. Wysłałem kod produkcyjny, więc jestem pewien, że działa. Wygląda trochę inaczej, ale kod, który opublikowałem wczoraj, też powinien działać.
DHN

Dzięki za wyjaśnienie tego. Okazało się, że wywołałem niewłaściwą przestrzeń nazw: S. Czy muszę tylko utworzyć datatriggeri przypisać go do przycisku, aby działał? Jeszcze raz przepraszam za pytanie nooby.
WPFNoob

Dzięki - cóż, jestem po prostu świadomy tego, że zadaję zbyt wiele pytań, które mogą wydawać się głupie i głupie, i marnuję czas ludzi! Ale wracając do mojego pytania. Po wszystkim, o czym wspomniałeś, jak zamknąć okno? Użyj DataTrigger¬ and setting value true`?
WPFNoob

1
Cóż, to jest ta część, którą zostawiam tobie. ; O) pomyśleć o DataContextz Dialog. Spodziewałbym się, że maszyna wirtualna ustawiona jako DataContextzapewnia polecenie, które ustawia właściwość DialogResultlub cokolwiek, do czego się zobowiązałeś, truelub falsetak, że Dialogzamyka się.
DHN

13

Łatwy sposób

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Zaimplementuj do ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Dodaj pomocnika ogólnego menedżera okien

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

I zamknij to w ten sposób w ViewModel

WindowManager.CloseWindow(ViewID);

Bardzo fajne rozwiązanie.
DonBoitnott

Zmieniłem nieco WindowManagera, aby ustawić dialogresult podczas zamykania publicznego statycznego void void CloseWindow (identyfikator Guid, bool dialogResult) {foreach (okno okna w Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} nazwij to tak: WindowManager.CloseWindow (_viewId, true);
lebhero

Fajne rozwiązanie, choć zapewnia ścisłe sprzężenie między modelem widoku a WindowManager, które z kolei jest ściśle powiązane View(pod względem PresentationFramework). Byłoby lepiej, gdyby WindowManagerusługa została przekazana do viewmodel za pośrednictwem interfejsu. Wtedy możesz (powiedzmy) łatwo przenieść swoje rozwiązanie na inną platformę.
Spook

4

Oto prosty przykład użycia komunikatora MVVM Light Messenger zamiast zdarzenia. Model widoku wysyła komunikat zamknięcia po kliknięciu przycisku:

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

    public RelayCommand QuitCommand { get; private set; }

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

Następnie jest odbierany w kodzie za oknem.

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

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

Czy możesz prosić o poradę, gdzie mogę znaleźć implementację CloseMessage?
Roman O

CloseMessage to po prostu pusta klasa, używana do identyfikowania typu wysyłanej wiadomości. (Może również zawierać złożone informacje dotyczące wiadomości, które nie są tutaj potrzebne).
IngoB

4

A co powiesz na to ?

ViewModel:

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

W swoim ViewModel użyj CloseAction (), aby zamknąć okno, tak jak w powyższym przykładzie.

Widok:

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

Wiem, że to stary post, chyba nikt by tak daleko nie przewinął, wiem, że nie. Więc po godzinach próbowania różnych rzeczy znalazłem tego bloga i koleś go zabił. Najprościej to zrobić, wypróbowałem i działa jak urok.

Blog

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

...

Dodaj właściwość Action do ViewModel, ale zdefiniuj ją z pliku związanego z kodem widoku. To pozwoli nam dynamicznie zdefiniować odniesienie w ViewModel, które wskazuje na View.

W ViewModel po prostu dodamy:

public Action CloseAction { get; set; }

W Widoku zdefiniujemy to jako takie:

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 jest uszkodzony: /
gusmally obsługuje Monikę

@gusmally czy na pewno? Otworzyłem normalnie, spróbuj ponownie jkshay.com/…
Serlok

2

Możesz utworzyć nową procedurę obsługi zdarzeń w ViewModel w ten sposób.

public event EventHandler RequestClose;

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

Następnie zdefiniuj RelayCommand dla 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();
    }

Następnie w zestawie plików XAML

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

Ustaw DataContext w pliku xaml.cs i zasubskrybuj utworzone przez nas zdarzenie.

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

Zamiast zdarzenia użyłem komunikatora MVVM Light Messenger.
Hamish Gunn

1

Mój proponowany sposób to zdarzenie Declare w ViewModel i użycie blend InvokeMethodAction, jak poniżej.

Przykładowy 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 Interfejs zamykany jest jak poniżej, ale nie wymaga wykonywania tej czynności. ICloseable pomoże w tworzeniu usługi widoku ogólnego, więc jeśli konstruujesz widok i ViewModel za pomocą iniekcji zależności, to możesz zrobić

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

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

A poniżej jest Xaml, możesz użyć tego Xaml, nawet jeśli nie zaimplementujesz interfejsu, będzie on potrzebował tylko Twojego modelu widoku do podniesienia CloseRquested.

<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

Możesz użyć Messengerz zestawu narzędzi MVVMLight. w ViewModelwyślij wiadomość taką jak ta:
Messenger.Default.Send(new NotificationMessage("Close"));
następnie w kodzie systemu Windows za, po InitializeComponent, zarejestruj się na tę wiadomość w następujący sposób:

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

więcej informacji na temat zestawu narzędzi MVVMLight można znaleźć tutaj: Zestaw narzędzi MVVMLight na Codeplex

Zauważ, że w MVVM nie ma reguły „bez kodu związanego z kodem” i możesz rejestrować wiadomości w widoku związanym z kodem.


0

To proste. Możesz utworzyć własną klasę ViewModel dla logowania - LoginViewModel. Możesz utworzyć widok var dialog = new UserView (); wewnątrz modelu LoginViewModel. I możesz ustawić Command LoginCommand w przycisku.

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

i

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

Klasa ViewModel:

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
Tak, to również prawidłowe rozwiązanie. Ale jeśli chcesz trzymać się MVVM i oddzielenia maszyn wirtualnych i widoków, złamiesz ten wzorzec.
DHN

Cześć @misak - próbując zaimplementować Twoje rozwiązanie (podobnie jak inne odpowiedzi), rzuca znak Object reference not set to an instance of an object.dla metody CloseLoginView. Jakieś sugestie, jak rozwiązać ten problem?
WPFNoob

@WPFNoob - ponownie podaję to rozwiązanie. Przykład działa poprawnie. Czy chcesz wysłać kompletne rozwiązanie Visual Studio na e-mail?
misak

@WPFNoob - widzę problem. Tworzysz instancję jako var dialog = new UserView () ;. Wyczyść słowo kluczowe var (wystąpienie lokalne) nadpisuje wystąpienie globalne w LoginViewModel
misak

0

Oto sposób, w jaki zrobiłem to całkiem prosto:

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

Nie widzę nic złego w odpowiedzi, którą wybrałeś, pomyślałem, że może to być prostszy sposób!


8
Wymaga to, aby model ViewModel wiedział o Twoim widoku i odnosił się do niego.
AndrewS

@AndrewS dlaczego to jest złe?
thestephenstanton

9
Aby postępować zgodnie ze wzorcem MVVM, ViewModel nie powinien wiedzieć o widoku.
MetalMikester

1
Aby to rozwinąć, celem MVVM jest przetestowanie większości jednostki kodu GUI. Widoki mają mnóstwo zależności, które uniemożliwiają ich testowanie jednostkowe. ViewModels powinny być testowalne jednostkowo, ale jeśli dasz im bezpośrednią zależność od widoku, nie będą.
ILMTitan

Aby to jeszcze bardziej rozwinąć, odpowiednio napisany MVVM umożliwia łatwą migrację rozwiązania na inną platformę. W szczególności powinno być możliwe ponowne użycie modeli widoku bez żadnych zmian. W takim przypadku, jeśli przeniesiesz swoje rozwiązanie na Androida, nie zadziała, ponieważ Android nie ma koncepcji okna. -1 dla rozwiązania przełamującego MVVM.
Spook

0

Możesz traktować okno jako usługę (np. Usługę UI) i przekazać się do viewmodelu poprzez interfejs , tak jak:

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

To rozwiązanie ma większość zalet przekazywania samego widoku do viewmodel bez wad polegających na przerywaniu MVVM, ponieważ chociaż fizyczny widok jest przekazywany do viewmodel, ten drugi nadal nie wie o pierwszym, widzi tylko niektóre IMainWindowAccess. Na przykład, gdybyśmy chcieli przenieść to rozwiązanie na inną platformę, byłaby to tylko kwestia IMainWindowAccessprawidłowego wdrożenia , powiedzmy, pliku Activity.

Publikuję tutaj rozwiązanie, aby zaproponować inne podejście niż zdarzenia (chociaż jest w rzeczywistości bardzo podobne), ponieważ wydaje się nieco prostsze niż zdarzenia do zaimplementowania (dołączanie / odłączanie itp.), Ale nadal ładnie dopasowuje się do wzorca MVVM.


-1

Możesz zamknąć bieżące okno, używając następującego kodu:

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

6
Jeśli masz więcej niż jedno okno, może to spowodować zamknięcie niewłaściwego okna.
Sasha

17
o Boże!
zabiłeś

-7

System.Environment.Exit (0); w widoku model zadziała.


6
Nie, nie będzie. Spowoduje to zamknięcie aplikacji i nie zamknięcie bieżącego okna.
Tilak

rozwiązało to mój problem, ponieważ zamknięcie mainWindow (do mnie) == wyjście z aplikacji. Wszystkie proponowane metody, z wyjątkiem tej, miały trudne punkty, gdy były wywoływane z różnych wątków; ale to podejście nie obchodzi, kto jest wątkiem dzwoniącym :) to było wszystko, czego potrzebowałem!
Hamed

BuAHahahAHahahAha, przepraszam, nie mogłem się oprzeć
L.Trabacchin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.