Đóng cửa sổ từ ViewModel


95

Tôi đang tạo Đăng nhập bằng cách sử dụng window controlđể cho phép người dùng đăng nhập vào WPFứng dụng mà tôi đang tạo.

Cho đến nay, tôi đã tạo ra một phương pháp kiểm tra xem liệu người dùng đã nhập đúng thông tin đăng nhập cho usernamepasswordtrong một textboxtrên màn hình đăng nhập, bindinghai hay không properties.

Tôi đã đạt được điều này bằng cách tạo ra một boolphương pháp, như vậy;

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

Tôi cũng có một nút commandmà tôi bindđể nút của tôi trong xamltương tự như vậy;

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

Khi tôi nhập tên người dùng và mật khẩu, nó sẽ thực thi mã đã chiếm đoạt, cho dù nó đúng hay sai. Nhưng làm cách nào để đóng cửa sổ này từ ViewModel khi cả tên người dùng và mật khẩu đều đúng?

Trước đây tôi đã thử sử dụng một dialog modalnhưng nó không hoàn toàn hiệu quả. Hơn nữa, trong app.xaml của tôi, tôi đã làm điều gì đó giống như sau, tải trang đăng nhập trước, sau đó khi đúng, tải ứng dụng thực tế.

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

Câu hỏi: Làm cách nào để đóng Đăng nhập Window controltừ ViewModel?

Cảm ơn trước.


Câu trả lời:


149

Bạn có thể chuyển cửa sổ tới ViewModel của mình bằng cách sử dụng CommandParameter. Xem Ví dụ của tôi bên dưới.

Tôi đã triển khai một CloseWindowPhương pháp lấy Windows làm tham số và đóng nó. Cửa sổ được chuyển đến ViewModel qua CommandParameter. Lưu ý rằng bạn cần xác định một x:Namecửa sổ sẽ đóng. Trong Cửa sổ XAML của tôi, tôi gọi phương thức này thông qua Commandvà chuyển chính cửa sổ đó làm tham số cho ViewModel bằng cách sử dụng 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();
    }
}

Lượt xem

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

Lưu ý rằng tôi đang sử dụng khung nhẹ MVVM, nhưng phần mềm chính áp dụng cho mọi ứng dụng wpf.

Giải pháp này vi phạm mẫu MVVM, vì mô hình xem không được biết bất kỳ điều gì về Triển khai giao diện người dùng. Nếu bạn muốn tuân thủ nghiêm ngặt mô hình lập trình MVVM, bạn phải tóm tắt kiểu xem với một giao diện.

Giải pháp phù hợp MVVM (EDIT2 cũ)

người dùng Crono đề cập đến một điểm hợp lệ trong phần bình luận:

Việc chuyển đối tượng Window vào mô hình xem sẽ phá vỡ IMHO của mẫu MVVM, vì nó buộc vm của bạn biết nó đang được xem trong gì.

Bạn có thể khắc phục điều này bằng cách giới thiệu một giao diện chứa phương thức đóng.

Giao diện:

public interface ICloseable
{
    void Close();
}

ViewModel được tái cấu trúc của bạn sẽ trông giống như sau:

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

Bạn phải tham khảo và triển khai ICloseablegiao diện theo quan điểm của mình

Xem (Mã phía sau)

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

Câu trả lời cho câu hỏi ban đầu: (EDIT1 cũ)

Nút Đăng nhập của bạn (Đã thêm CommandParameter):

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

Ma cua ban:

 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
Cảm ơn vì bản cập nhật @Joel. Một câu hỏi cuối cùng, do phương thức nhận một tham số của Window và khi tôi gọi phương thức đó trong lệnh của mình, nó sẽ yêu cầu một tham số, tôi có tạo một tham số Window cục bộ được gọi cho phương thức không, ví dụ; private void LoginExecute(){this.CheckLogin();}<- CheckLogin cần tham số.
WPFNoob

xin lỗi tôi không hiểu, bạn có thể làm rõ câu hỏi của bạn một chút được không?
Joel

14
Nếu bạn không thích đặt tên cửa sổ của bạn, bạn cũng có thể ràng buộc các tham số như thế này:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
Việc chuyển Windowđối tượng đến mô hình chế độ xem sẽ phá vỡ IMHO mẫu MVVM, vì nó buộc vm của bạn biết nó đang được xem ở đâu. Nếu chế độ xem là một tab được gắn vào trong giao diện MDI thì sao? Cách thích hợp để thực hiện IMHO này là chuyển một số loại giao diện IUIHost triển khai phương thức Đóng và có bất kỳ chế độ xem nào bạn muốn hiển thị vm của bạn triển khai nó.
Crono

2
Không sao cả vì giao diện ẩn việc triển khai cụ thể cho ViewModel. ViewModel không biết gì về khung nhìn ngoại trừ việc nó thực hiện phương thức Close (). Do đó, khung nhìn có thể là bất kỳ thứ gì: Cửa sổ WPF, Biểu mẫu WinForms, Ứng dụng UWP hoặc thậm chí là Lưới WPF. Nó tách chế độ xem khỏi chế độ xem.
Joel

34

Giữ nguyên MVVM, tôi nghĩ rằng việc sử dụng Behaviors từ Blend SDK (System.Windows.Interactivity) hoặc một yêu cầu tương tác tùy chỉnh từ Prism có thể hoạt động thực sự tốt cho loại tình huống này.

Nếu đi theo lộ trình Hành vi, đây là ý tưởng chung:

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

Sau đó, trong cửa sổ của bạn, bạn sẽ chỉ ràng buộc CloseTrigger với một giá trị boolean sẽ được đặt khi bạn muốn cửa sổ đóng lại.

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

Cuối cùng, DataContext / ViewModel của bạn sẽ có một thuộc tính mà bạn đặt khi muốn đóng cửa sổ như sau:

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

(đặt Window.DataContext = new MainWindowViewModel ())


Cảm ơn câu trả lời @Steve, bạn đã đề cập đến việc liên kết CloseTrigger với một booleangiá trị. Khi bạn nói điều đó, bạn có ý muốn tôi tạo ra một DataTriggerđể đạt được nó?
WPFNoob

Xin lỗi, lẽ ra tôi phải nói rõ ràng hơn - tôi có một thuộc tính trên mô hình xem của mình (trong ví dụ trên, một thuộc tính có tên là CloseTrigger) sẽ được đặt thành true, cuối cùng sẽ kích hoạt hành vi. Tôi đã cập nhật câu trả lời
Steve Van Treeck

Điều này đã hiệu quả, nhưng tôi phải thay đổi cách tải ứng dụng của mình. Bởi vì tôi đang sử dụng Window cho ứng dụng chính của mình, nó đã giết tất cả các cửa sổ con. Cảm ơn.
WPFNoob

Đặt thuộc tính thành true để thực hiện một hành động là IMO có mùi.
Josh Noe

33

Tôi thường đặt một sự kiện trên mô hình khung nhìn khi tôi cần làm điều này và sau đó nối nó với Window.Close()khi liên kết mô hình khung nhìn với cửa sổ

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

Và khi tạo cửa sổ đăng nhập

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

loginWindow.ShowDialog(); 

11
Người đại diện ẩn danh nhanh chóng được viết, nhưng cần lưu ý rằng sự kiện không thể được hủy đăng ký (có thể là một vấn đề hoặc có thể không). Thường tốt hơn với một trình xử lý sự kiện chính thức.
Mathieu Guindon

Tôi thích điều này nhất. Có là anyway khó để tránh chế biến đặc biệt khi hiển thị cửa sổ (ví dụ Loaded, ContentRenderedđối với cửa sổ chính, các dịch vụ thoại, vv), thêm một chút để nó thông qua sự kiện ViewModel là khá sạch như đối với tôi. 3 dòng mã không thực sự cần bất kỳ giải pháp tái sử dụng nào. PS: dù sao thì MVVM thuần túy cũng dành cho dân mọt sách.
Sinatr

Cậu bé này đã giúp tôi.
Dimitri

Đây là câu trả lời tốt hơn nhiều so với câu trả lời được chấp nhận, vì nó không phá vỡ mô hình MVVM.
Spook

22

nó có thể muộn, nhưng đây là câu trả lời của tôi

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

1
tại sao đây không phải là câu trả lời thực tế?
user2529011

1
@ user2529011 một số người, ít nhất, sẽ phàn nàn rằng viewmodel không nên biết gì về Application.Current.Windows
gusmally hỗ trợ Monica

-1. Mô hình khung nhìn không nên biết bất cứ điều gì về khung nhìn. Bạn cũng có thể viết nó trong mã phía sau cho vấn đề đó.
Alejandro

13

Đây là một cái gì đó tôi đã sử dụng trong một số dự án. Nó có thể trông giống như một vụ hack, nhưng nó hoạt động tốt.

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

Bây giờ bạn có thể liên kết DialogResultvới một máy ảo và đặt giá trị của một thuộc tính. Các Windowsẽ đóng cửa, khi giá trị được thiết lập.

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

Đây là phần tóm tắt về những gì đang chạy trong môi trường sản xuất của chúng tôi

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

Như bạn có thể thấy, tôi đang khai báo không gian tên xmlns:hlp="clr-namespace:AC.Frontend.Helper"trước và sau đó là ràng buộc hlp:AttachedProperties.DialogResult="{Binding DialogResult}".

Các AttachedPropertyngoại hình như thế này. Nó không giống như tôi đã đăng ngày hôm qua, nhưng IMHO nó sẽ không có bất kỳ ảnh hưởng nào.

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
}

Không, đó không phải là một câu hỏi ngớ ngẩn. Chỉ cần đặt khai báo của ràng buộc trong <Window />phần tử như tôi đã minh họa trong đoạn trích của tôi. Tôi đã quá lười biếng để viết phần còn lại (khai báo không gian tên, v.v.), phần này cũng thường được khai báo ở đó.
DHN

1
Xin vui lòng tham khảo bản chỉnh sửa của tôi. Tôi đã đăng mã sản xuất, vì vậy tôi chắc chắn rằng nó đang hoạt động. Nó trông hơi khác một chút, nhưng mã tôi đã đăng ngày hôm qua cũng sẽ hoạt động.
DHN

Cám ơn giải thích rõ ràng. Hóa ra là tôi đã gọi nhầm không gian tên: S. Tôi chỉ cần tạo datatriggervà gán nó vào nút để làm cho nó hoạt động? Một lần nữa xin lỗi vì câu hỏi nooby.
WPFNoob

Cảm ơn - tôi chỉ biết tự biết mình đã hỏi quá nhiều câu hỏi có vẻ ngớ ngẩn và ngu ngốc và lãng phí thời gian của mọi người! Nhưng quay lại câu hỏi của tôi. Sau tất cả những gì bạn đã đề cập, làm cách nào để đóng cửa sổ? Sử dụng một DataTrigger¬ and setting value đúng`?
WPFNoob

1
Vậy là xong phần, tôi giao cho bạn. ; o) Hãy suy nghĩ về DataContextcủa Dialog. Tôi mong đợi rằng máy ảo đặt dưới dạng DataContextcung cấp một lệnh, đặt thuộc tính DialogResulthoặc bất kỳ thứ gì bạn đã ràng buộc truehoặc falseđể Dialogđóng.
DHN

13

Cách dễ dàng

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

Triển khai cho ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

Thêm trình trợ giúp trình quản lý cửa sổ chung

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

Và đóng nó như thế này trong viewmodel

WindowManager.CloseWindow(ViewID);

Một giải pháp rất tốt.
DonBoitnott

tôi đã thay đổi tùy chọn WindowManager để đặt hộp thoại khi đóng win public static void CloseWindow (Guid id, bool hộp thoạiResult) {foreach (Window window trong Application.Current.Windows) {var w_id = window.DataContext as IRequireViewIdentification; if (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = hộp thoạiResult; window.Close (); }}} gọi nó như sau: WindowManager.CloseWindow (_viewId, true);
lebhero

Tuy nhiên, giải pháp tốt giúp kết hợp chặt chẽ giữa mô hình xem và WindowManager, đến lượt nó được kết hợp chặt chẽ với View(về mặt PresentationFramework). Sẽ tốt hơn nếu WindowManagermột dịch vụ được chuyển sang chế độ xem qua một giao diện. Sau đó, bạn có thể (giả sử) di chuyển giải pháp của mình sang một nền tảng khác một cách dễ dàng.
Spook

4

Đây là một ví dụ đơn giản sử dụng MVVM Light Messenger thay vì một sự kiện. Mô hình chế độ xem sẽ gửi một thông báo đóng khi một nút được nhấp vào:

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

    public RelayCommand QuitCommand { get; private set; }

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

Sau đó, nó được nhận trong mã phía sau của cửa sổ.

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

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

Bạn có thể vui lòng cho lời khuyên, nơi tôi có thể tìm cách triển khai CloseMessage?
Roman O

CloseMessage chỉ là một lớp trống, được sử dụng để xác định loại tin nhắn được gửi đi. (Nó cũng có thể chứa thông tin tin nhắn phức tạp, không cần thiết ở đây.)
IngoB

4

Làm thế nào về điều này ?

ViewModel:

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

Trong ViewModel của bạn, hãy sử dụng CloseAction () để đóng cửa sổ giống như trong ví dụ trên.

Lượt xem:

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

Tôi biết đây là một bài viết cũ, có lẽ sẽ không ai di chuyển đến đây, tôi biết tôi đã không. Vì vậy, sau nhiều giờ thử các công cụ khác nhau, tôi tìm thấy blog này và anh bạn đã giết nó. Cách đơn giản nhất để làm điều này, đã thử và nó hoạt động như một sự quyến rũ.

Blog

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

...

thêm thuộc tính Action vào ViewModel, nhưng xác định nó từ tệp mã phía sau của View. Điều này sẽ cho phép chúng tôi xác định động một tham chiếu trên ViewModel trỏ đến View.

Trên ViewModel, chúng tôi chỉ cần thêm:

public Action CloseAction { get; set; }

Và trên Chế độ xem, chúng tôi sẽ định nghĩa nó như sau:

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

Liên kết bị phá vỡ: /
gusmally hỗ trợ Monica

@gusmally bạn có chắc không? Tôi mở nó normaly, hãy thử lại jkshay.com/...
Serlok

2

Bạn có thể tạo trình xử lý Sự kiện mới trong ViewModel như thế này.

public event EventHandler RequestClose;

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

Sau đó xác định RelayCommand cho 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();
    }

Sau đó, trong tập hợp tệp XAML

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

Đặt DataContext trong Tệp xaml.cs và Đăng ký sự kiện chúng tôi đã tạo.

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

Tôi đã sử dụng MVVM Light Messenger thay vì sự kiện.
Hamish Gunn

1

Cách phổ biến của tôi là Khai báo sự kiện trong ViewModel và sử dụng kết hợp InvokeMethodAction như bên dưới.

Chế độ xem mẫu

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

Giao diện I Closable như bên dưới nhưng không yêu cầu thực hiện thao tác này. IClosable sẽ giúp tạo dịch vụ chế độ xem chung, vì vậy nếu bạn tạo chế độ xem và ViewModel bằng cách tiêm phụ thuộc thì những gì bạn có thể làm là

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

Sử dụng IClosable

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

Và bên dưới là Xaml, Bạn có thể sử dụng xaml này ngay cả khi bạn không triển khai giao diện, nó sẽ chỉ cần mô hình khung nhìn của bạn để nâng 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

Bạn có thể sử dụng Messengertừ bộ công cụ MVVMLight. khi bạn ViewModelgửi một thông báo như thế này:
Messenger.Default.Send(new NotificationMessage("Close"));
sau đó trong mã cửa sổ của bạn phía sau, sau InitializeComponent, đăng ký cho thông báo đó như sau:

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

bạn có thể tìm thêm về bộ công cụ MVVMLight tại đây: Bộ công cụ MVVMLight trên Codeplex

Lưu ý rằng không có quy tắc "không có mã phía sau nào cả" trong MVVM và bạn có thể đăng ký các tin nhắn trong chế độ xem có mã phía sau.


0

Nó đơn giản. Bạn có thể tạo lớp ViewModel của riêng mình cho Đăng nhập - LoginViewModel. Bạn có thể tạo view var hộp thoại = new UserView (); bên trong LoginViewModel của bạn. Và bạn có thể thiết lập Command LoginCommand vào nút.

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

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

Lớp 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
Vâng, đó cũng là một giải pháp hợp lệ. Nhưng nếu bạn muốn gắn bó với MVVM và việc tách các máy ảo và lượt xem, bạn sẽ phá vỡ mô hình.
DHN

Xin chào @misak - đang cố gắng triển khai giải pháp của bạn (giống như các câu trả lời khác), nó ném một Object reference not set to an instance of an object.phương thức CloseLoginView. Bất kỳ đề xuất làm thế nào để giải quyết vấn đề đó?
WPFNoob

@WPFNoob - Tôi lại khay giải pháp này. Ví dụ hoạt động chính xác. Bạn có muốn gửi giải pháp studio trực quan hoàn chỉnh qua email không?
misak

@WPFNoob - Tôi thấy vấn đề. Bạn đang tạo phiên bản dưới dạng var hộp thoại = new UserView () ;. Xóa từ khóa var (phiên bản cục bộ) ghi đè phiên bản toàn cầu trong LoginViewModel
sai

0

Đây là một cách tôi đã làm khá đơn giản:

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

Tôi không thấy có gì sai với câu trả lời bạn đã chọn, tôi chỉ nghĩ rằng đây có thể là một cách đơn giản hơn để làm điều đó!


8
Điều này yêu cầu ViewModel của bạn phải biết và tham chiếu đến Chế độ xem của bạn.
AndrewS

@AndrewS tại sao lại tệ như vậy?
thestephenstanton

9
Để làm theo mẫu MVVM, ViewModel không nên biết về View.
MetalMikester

1
Để mở rộng điều này, điểm của MVVM là làm cho hầu hết đơn vị mã GUI của bạn có thể kiểm tra được. Chế độ xem có rất nhiều phụ thuộc khiến chúng không thể kiểm tra đơn vị. ViewModels phải là đơn vị có thể kiểm tra được, nhưng nếu bạn cho chúng phụ thuộc trực tiếp vào chế độ xem, chúng sẽ không như vậy.
ILMTitan

Và để mở rộng hơn nữa, MVVM được viết đúng cách cho phép bạn di chuyển giải pháp sang một nền tảng khác một cách dễ dàng. Đặc biệt, bạn có thể sử dụng lại các mô hình xem của mình mà không có bất kỳ thay đổi nào. Trong trường hợp này nếu bạn chuyển giải pháp của mình sang Android, nó sẽ không hoạt động vì Android không có khái niệm về Cửa sổ. -1 cho giải pháp phá vỡ MVVM.
Spook

0

Bạn có thể coi cửa sổ như một dịch vụ (ví dụ: dịch vụ giao diện người dùng) và chuyển chính nó sang chế độ xem thông qua một giao diện , chẳng hạn như:

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

Giải pháp này có hầu hết các mặt lợi của việc tự chuyển chế độ xem sang chế độ xem mà không có nhược điểm là phá vỡ MVVM, bởi vì mặc dù chế độ xem vật lý được chuyển sang chế độ xem, nhưng cái sau vẫn không biết về cái trước, nó chỉ thấy một số IMainWindowAccess. Vì vậy, ví dụ: nếu chúng tôi muốn di chuyển giải pháp này sang nền tảng khác, nó sẽ chỉ là vấn đề triển khai IMainWindowAccessđúng cách cho, ví dụ, một Activity.

Tôi đăng giải pháp ở đây để đề xuất một cách tiếp cận khác với các sự kiện (mặc dù nó thực sự rất giống nhau), bởi vì nó có vẻ đơn giản hơn một chút so với các sự kiện để triển khai (đính kèm / tách rời, v.v.), nhưng vẫn phù hợp tốt với mẫu MVVM.


-1

Bạn có thể đóng cửa sổ hiện tại chỉ bằng cách sử dụng mã sau:

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

6
Nếu bạn có nhiều cửa sổ, điều này có thể đóng sai cửa sổ.
Sasha

17
Ôi Chúa ơi! bạn đã tàn sát MVVM
Hossein Shahdoost

-7

System.Enosystem.Exit (0); trong chế độ xem mô hình sẽ hoạt động.


6
Không, nó sẽ không. Nó sẽ thoát khỏi ứng dụng và không đóng cửa sổ hiện tại.
Tilak

điều này đã giải quyết vấn đề của tôi, vì đóng mainWindow (với tôi) == thoát ứng dụng. Tất cả các phương pháp được đề xuất ngoại trừ phương pháp này đều có điểm khó khi được gọi từ các chủ đề khác nhau; nhưng cách tiếp cận này không thực sự quan tâm chủ đề người gọi là ai :) đó là tất cả những gì tôi cần!
Hamed

BuAHahahAHahahAha xin lỗi không thể cưỡng lại
L.Trabacchin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.