ปิดหน้าต่างจาก ViewModel


97

ฉันกำลังสร้างการเข้าสู่ระบบโดยใช้window controlเพื่ออนุญาตให้ผู้ใช้ล็อกอินเข้าสู่WPFแอปพลิเคชันที่ฉันกำลังสร้าง

จนถึงตอนนี้ผมได้สร้างวิธีการที่จะตรวจสอบว่าผู้ใช้จะได้เข้าในข้อมูลประจำตัวที่ถูกต้องสำหรับusernameและpasswordในtextboxบนหน้าจอเข้าสู่ระบบสองbindingproperties

ฉันประสบความสำเร็จโดยการสร้างboolวิธีการเช่นนั้น

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

ฉันยังมีcommandที่ฉันbindไปที่ปุ่มของฉันภายในxamlเช่นนั้น

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

เมื่อฉันป้อนชื่อผู้ใช้และรหัสผ่านมันจะเรียกใช้รหัสที่เหมาะสมไม่ว่าจะถูกหรือผิด แต่ฉันจะปิดหน้าต่างนี้จาก ViewModel ได้อย่างไรเมื่อทั้งชื่อผู้ใช้และรหัสผ่านถูกต้อง

ก่อนหน้านี้ฉันได้ลองใช้dialog modalแต่มันไม่ได้ผล นอกจากนี้ภายใน app.xaml ของฉันฉันได้ทำสิ่งต่อไปนี้ซึ่งจะโหลดหน้าเข้าสู่ระบบก่อนจากนั้นเมื่อเป็นจริงโหลดแอปพลิเคชันจริง

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

คำถาม: ฉันจะปิดการเข้าสู่ระบบWindow controlจาก ViewModel ได้อย่างไร?

ขอบคุณล่วงหน้า.


คำตอบ:


154

คุณสามารถส่งผ่านหน้าต่างไปยัง ViewModel ของคุณโดยใช้ไฟล์CommandParameter. ดูตัวอย่างของฉันด้านล่าง

ฉันได้ใช้CloseWindowวิธีการที่ใช้ Windows เป็นพารามิเตอร์และปิดมัน หน้าต่างถูกส่งผ่านไปยัง ViewModel CommandParameterผ่าน โปรดทราบว่าคุณต้องกำหนดx:Nameสำหรับหน้าต่างที่ควรจะปิด ในหน้าต่าง XAML ของฉันฉันเรียกวิธีการนี้ทางCommandและผ่านหน้าต่างของตัวเองเป็นพารามิเตอร์ไป 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();
    }
}

ดู

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

โปรดทราบว่าฉันใช้ MVVM light framework แต่หลักการใช้กับแอปพลิเคชัน wpf ทุกตัว

โซลูชันนี้ละเมิดรูปแบบ MVVM เนื่องจาก view-model ไม่ควรรู้อะไรเกี่ยวกับการใช้งาน UI หากคุณต้องการปฏิบัติตามกระบวนทัศน์การเขียนโปรแกรม MVVM อย่างเคร่งครัดคุณต้องแยกประเภทของมุมมองด้วยอินเทอร์เฟซ

โซลูชันที่สอดคล้องกับ MVVM (EDIT2 เดิม)

ผู้ใช้Cronoกล่าวถึงจุดที่ถูกต้องในส่วนความคิดเห็น:

การส่งผ่านวัตถุ Window ไปยังโมเดลมุมมองจะทำลาย IMHO รูปแบบ MVVM เนื่องจากบังคับให้ vm ของคุณทราบว่ากำลังดูอะไรอยู่

คุณสามารถแก้ไขได้โดยการแนะนำอินเทอร์เฟซที่มีวิธีการปิด

อินเตอร์เฟซ:

public interface ICloseable
{
    void Close();
}

ViewModel ที่ปรับโครงสร้างใหม่ของคุณจะมีลักษณะดังนี้:

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

คุณต้องอ้างอิงและใช้ICloseableอินเทอร์เฟซในมุมมองของคุณ

ดู (รหัสอยู่ข้างหลัง)

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

ตอบคำถามเดิม: (EDIT1 เดิม)

ปุ่มเข้าสู่ระบบของคุณ (เพิ่ม CommandParameter):

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

รหัสของคุณ:

 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
ขอบคุณสำหรับการอัปเดต @Joel คำถามสุดท้ายเนื่องจากเมธอดใช้พารามิเตอร์ของ Window และเมื่อฉันเรียกใช้เมธอดนั้นภายในคำสั่งของฉันมันคาดว่าพารามิเตอร์ฉันจะสร้างพารามิเตอร์ Window ภายในที่เรียกใช้สำหรับเมธอดเช่น; private void LoginExecute(){this.CheckLogin();}<- CheckLogin จำเป็นต้องใช้พารามิเตอร์
WPFNoob

ขอโทษที่ฉันไม่เข้าใจคุณช่วยชี้แจงคำถามของคุณหน่อยได้ไหม
Joel

14
หากคุณไม่ชอบตั้งชื่อ windows ของคุณคุณสามารถผูกพารามิเตอร์ได้เช่นนี้:CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Jacco Dieleman

33
การส่งผ่านWindowวัตถุไปยังโมเดลมุมมองจะทำลาย IMHO รูปแบบ MVVM เนื่องจากบังคับให้ vm ของคุณทราบว่ากำลังดูอะไรอยู่จะเกิดอะไรขึ้นถ้ามุมมองเป็นแท็บเชื่อมต่อในอินเทอร์เฟซ MDI แทน วิธีที่เหมาะสมในการทำ IMHO นี้คือการส่งผ่านอินเทอร์เฟซ IUIHost บางประเภทที่ใช้วิธีการปิดและมีมุมมองใดก็ได้ที่คุณต้องการแสดงให้ vm ใช้งานได้
Crono

2
ไม่เป็นไรเพราะอินเทอร์เฟซซ่อนการนำไปใช้งานจริงกับ ViewModel ViewModel ไม่ทราบอะไรเกี่ยวกับมุมมองยกเว้นว่าจะใช้เมธอด Close () ดังนั้นมุมมองอาจเป็นอะไรก็ได้: WPF Window, WinForms Form, UWP Application หรือแม้แต่ WPF Grid มันแยกมุมมองจาก viewmodel
Joel

35

ฉันมักจะใส่เหตุการณ์ในโมเดลมุมมองเมื่อฉันต้องการทำสิ่งนี้แล้วต่อเข้ากับWindow.Close()เมื่อผูกโมเดลมุมมองกับหน้าต่าง

public class LoginViewModel
{
    public event EventHandler OnRequestClose;

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

และเมื่อสร้างหน้าต่างเข้าสู่ระบบ

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

loginWindow.ShowDialog(); 

11
ผู้รับมอบสิทธิ์ที่ไม่ระบุชื่อถูกเขียนขึ้นอย่างรวดเร็ว แต่ควรสังเกตว่าเหตุการณ์นั้นไม่สามารถยกเลิกการลงทะเบียนได้ (ซึ่งอาจเป็นปัญหาหรือไม่ก็ได้) โดยปกติแล้วจะดีกว่าเมื่อใช้ตัวจัดการเหตุการณ์แบบเต็มรูปแบบ
Mathieu Guindon

1
ฉันชอบเรื่องนี้มากที่สุด มีอยู่แล้วเป็นเรื่องยากที่จะหลีกเลี่ยงการประมวลผลพิเศษเมื่อแสดงหน้าต่าง (เช่นLoaded, ContentRenderedสำหรับหน้าต่างหลักบริการโต้ตอบ ฯลฯ ), การเพิ่มบิตจะผ่านเหตุการณ์ ViewModel สวยสะอาดเป็นสำหรับฉัน โค้ด 3 บรรทัดไม่จำเป็นต้องมีโซลูชันที่สามารถใช้ซ้ำได้ PS: MVVM ที่บริสุทธิ์นั้นมีไว้สำหรับผู้สนใจอยู่แล้ว
Sinatr

เด็กชายช่วยฉันด้วย
Dimitri

1
นี่เป็นคำตอบที่ดีกว่าที่ยอมรับเพราะมันไม่ทำลายรูปแบบ MVVM
Spook

35

การใช้ MVVM ฉันคิดว่าการใช้ Behaviors จาก Blend SDK (System.Windows.Interactivity) หรือคำขอการโต้ตอบแบบกำหนดเองจาก Prism อาจทำงานได้ดีสำหรับสถานการณ์เช่นนี้

หากไปตามเส้นทางพฤติกรรมนี่คือแนวคิดทั่วไป:

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

จากนั้นในหน้าต่างของคุณคุณจะผูก CloseTrigger กับค่าบูลีนที่จะตั้งค่าเมื่อคุณต้องการให้หน้าต่างปิด

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

สุดท้าย DataContext / ViewModel ของคุณจะมีคุณสมบัติที่คุณตั้งไว้เมื่อคุณต้องการให้หน้าต่างปิดเช่นนี้:

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

(ตั้งค่า Window.DataContext = MainWindowViewModel ใหม่ ())


ขอบคุณสำหรับการตอบกลับ @Steve คุณได้กล่าวถึงการผูก CloseTrigger กับbooleanค่า เมื่อคุณพูดอย่างนั้นคุณหมายถึงให้ฉันสร้างDataTriggerเพื่อบรรลุเป้าหมายหรือไม่?
WPFNoob

ขออภัยฉันควรจะอธิบายให้ชัดเจนกว่านี้ - ฉันมีคุณสมบัติใน viewmodel ของฉัน (ในตัวอย่างข้างต้นชื่อ CloseTrigger) ที่จะถูกตั้งค่าเป็นจริงซึ่งจะทำให้เกิดพฤติกรรม ฉันอัปเดตคำตอบ
Steve Van Treeck

วิธีนี้ได้ผล แต่ฉันต้องเปลี่ยนวิธีโหลดแอปพลิเคชันของฉัน เนื่องจากฉันใช้ Window สำหรับแอปพลิเคชันหลักมันจึงฆ่าหน้าต่างลูกทั้งหมด ขอบคุณ.
WPFNoob

การตั้งค่าคุณสมบัติเป็นจริงเพื่อดำเนินการจะส่งกลิ่น IMO
Josh Noe

22

มันอาจจะสายไป แต่นี่คือคำตอบของฉัน

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

1
เหตุใดจึงไม่ใช่คำตอบที่แท้จริง
user2529011

1
@ user2529011 อย่างน้อยก็จะบ่นว่า viewmodel ไม่ควรรู้อะไรเกี่ยวกับ Application.Current.Windows
gusmally รองรับ Monica

-1. โมเดลมุมมองไม่ควรรู้อะไรเลยเกี่ยวกับมุมมอง คุณสามารถเขียนไว้ในโค้ดด้านหลังของเรื่องนั้นได้เช่นกัน
Alejandro

13

นี่คือสิ่งที่ฉันใช้ในหลายโครงการ อาจดูเหมือนแฮ็ค แต่ก็ใช้ได้ดี

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

ตอนนี้คุณสามารถเชื่อมโยงDialogResultกับ VM และกำหนดค่าของคุณสมบัติได้ Windowจะปิดเมื่อมีค่าเป็นชุด

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

นี่คือนามธรรมของสิ่งที่กำลังทำงานอยู่ในสภาพแวดล้อมการผลิตของเรา

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

ที่คุณสามารถดูฉันประกาศ Namespace อย่างแรกและหลังจากนั้นมีผลผูกพันxmlns:hlp="clr-namespace:AC.Frontend.Helper"hlp:AttachedProperties.DialogResult="{Binding DialogResult}"

AttachedPropertyลักษณะเช่นนี้ มันไม่เหมือนกับที่ฉันโพสต์เมื่อวานนี้ แต่ IMHO ไม่น่าจะมีผลกระทบใด ๆ

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
}

ไม่มันไม่ใช่คำถามโง่ ๆ เพียงแค่ใส่คำประกาศการผูกไว้ใน<Window />องค์ประกอบตามที่ฉันแสดงไว้ในสนิปของฉัน ฉันขี้เกียจเกินไปที่จะเขียนส่วนที่เหลือ (การประกาศเนมสเปซ ฯลฯ ) ซึ่งมักจะประกาศไว้ที่นั่นด้วย
DHN

1
โปรดอ้างถึงการแก้ไขของฉัน ฉันโพสต์รหัสการผลิตดังนั้นฉันแน่ใจว่ามันใช้งานได้ มันดูแตกต่างกันเล็กน้อย แต่โค้ดที่ฉันโพสต์เมื่อวานก็ควรใช้งานได้เช่นกัน
DHN

ขอขอบคุณที่แจ้งให้ทราบ กลายเป็นว่าฉันเรียกเนมสเปซผิด: S. ฉันต้องสร้างdatatriggerและกำหนดให้กับปุ่มเพื่อให้ใช้งานได้หรือไม่? ขออภัยอีกครั้งสำหรับคำถาม nooby
WPFNoob

ขอบคุณ - ฉันแค่รู้ตัวเองว่าถามคำถามมากเกินไปซึ่งอาจดูโง่และโง่และเสียเวลาของผู้คน! แต่กลับไปที่คำถามของฉัน หลังจากทุกสิ่งที่คุณพูดถึงฉันจะปิดหน้าต่างได้อย่างไร ใช้DataTrigger¬ and setting value จริง `?
WPFNoob

1
นั่นคือส่วนหนึ่งฉันจะไปให้คุณ ; o) ลองนึกถึงDataContextไฟล์Dialog. ฉันคาดหวังว่า VM จะตั้งค่าเป็นDataContextคำสั่งซึ่งตั้งค่าคุณสมบัติDialogResultหรือสิ่งที่คุณผูกไว้trueหรือfalseเพื่อให้การDialogปิด
DHN

13

ทางที่ง่าย

public interface IRequireViewIdentification
{
    Guid ViewID { get; }
}

นำไปใช้กับ ViewModel

public class MyViewVM : IRequireViewIdentification
{
    private Guid _viewId;

    public Guid ViewID
    {
        get { return _viewId; }
    }

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

เพิ่มตัวช่วยจัดการหน้าต่างทั่วไป

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

แล้วปิดแบบนี้ใน viewmodel

WindowManager.CloseWindow(ViewID);

ทางออกที่ดีมาก
DonBoitnott

ฉันเปลี่ยน WindowManager เล็กน้อยเพื่อตั้งค่าไดอะล็อกผลลัพธ์เมื่อปิด win public static void CloseWindow (Guid id, bool dialogResult) {foreach (Window window ใน Application.Current.Windows) {var w_id = window.DataContext เป็น IRequireViewIdentification; ถ้า (w_id! = null && w_id.ViewID.Equals (id)) {window.DialogResult = dialogResult; window.Close (); }}} เรียกมันว่า: WindowManager.CloseWindow (_viewId, true);
lebhero

วิธีแก้ปัญหาที่ดีแม้ว่าจะทำให้การมีเพศสัมพันธ์ที่แน่นหนาระหว่าง viewmodel และWindowManagerซึ่งในทางกลับกันนั้นควบคู่ไปกับView(ในแง่ของPresentationFramework) อย่างแน่นหนา จะดีกว่าถ้าWindowManagerบริการส่งผ่านไปยัง viewmodel ผ่านอินเทอร์เฟซ จากนั้นคุณจะสามารถ (พูด) โยกย้ายโซลูชันของคุณไปยังแพลตฟอร์มอื่นได้อย่างง่ายดาย
Spook

4

นี่คือตัวอย่างง่ายๆโดยใช้ MVVM Light Messenger แทนเหตุการณ์ โมเดลมุมมองจะส่งข้อความปิดเมื่อคลิกปุ่ม:

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

    public RelayCommand QuitCommand { get; private set; }

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

จากนั้นจะได้รับรหัสที่อยู่ด้านหลังของหน้าต่าง

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

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

ขอคำแนะนำหน่อยได้ไหมว่าฉันจะหาการใช้งาน CloseMessage ได้ที่ไหน
Roman O

CloseMessage เป็นเพียงคลาสว่างที่ใช้เพื่อระบุประเภทของข้อความที่กำลังส่ง (อาจมีข้อความที่ซับซ้อนเช่นกันซึ่งไม่จำเป็นที่นี่)
IngoB

4

แล้วเรื่องนี้ล่ะ?

ดู

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

ใน ViewModel ของคุณให้ใช้ CloseAction () เพื่อปิดหน้าต่างเช่นเดียวกับในตัวอย่างด้านบน

ดู:

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

ฉันรู้ว่านี่เป็นโพสต์เก่าอาจไม่มีใครเลื่อนมาไกลขนาดนี้ฉันรู้ว่าฉันไม่ได้ทำ หลังจากลองใช้สิ่งต่างๆมาหลายชั่วโมงฉันก็พบบล็อกนี้และเพื่อนก็ฆ่าทิ้ง วิธีที่ง่ายที่สุดในการทำเช่นนี้ลองแล้วได้ผลเหมือนมีเสน่ห์

บล็อก

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

...

เพิ่มคุณสมบัติ Action ให้กับ ViewModel แต่กำหนดจากไฟล์ Code-behind ของ View สิ่งนี้จะช่วยให้เรากำหนดการอ้างอิงแบบไดนามิกบน ViewModel ที่ชี้ไปที่ View

ใน ViewModel เราจะเพิ่ม:

public Action CloseAction { get; set; }

และในมุมมองเราจะกำหนดเป็นดังนี้:

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


@gusmally คุณแน่ใจหรือ ฉันเปิดมันปกติแล้วลองอีกครั้งjkshay.com/…
Serlok

2

คุณสามารถสร้างตัวจัดการเหตุการณ์ใหม่ใน ViewModel เช่นนี้

public event EventHandler RequestClose;

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

จากนั้นกำหนด RelayCommand สำหรับ 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();
    }

จากนั้นในชุดไฟล์ XAML

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

ตั้งค่า DataContext ในไฟล์ xaml.cs และสมัครเข้าร่วมกิจกรรมที่เราสร้างขึ้น

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

ฉันใช้ MVVM Light Messenger แทนเหตุการณ์
Hamish Gunn

1

วิธีที่เป็นผู้เชี่ยวชาญของฉันคือ Declare event ใน ViewModel และใช้ผสมผสาน InvokeMethodAction ดังต่อไปนี้

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 Closeable อินเทอร์เฟซดังต่อไปนี้ แต่ไม่จำเป็นต้องดำเนินการนี้ ICloseable จะช่วยในการสร้างบริการมุมมองทั่วไปดังนั้นหากคุณสร้างมุมมองและ ViewModel โดยการฉีดแบบพึ่งพาสิ่งที่คุณสามารถทำได้คือ

internal interface ICloseable
{
    event EventHandler CloseRequested;
}

การใช้ 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();
        }

และด้านล่างนี้คือ Xaml คุณสามารถใช้ xaml นี้ได้แม้ว่าคุณจะไม่ได้ติดตั้งอินเทอร์เฟซ แต่จะต้องใช้โมเดลมุมมองของคุณเท่านั้นเพื่อเพิ่ม 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

คุณสามารถใช้Messengerจากชุดเครื่องมือ MVVMLight ในการViewModelส่งข้อความของคุณดังนี้:
Messenger.Default.Send(new NotificationMessage("Close"));
จากนั้นในรหัส windows ของคุณด้านหลังหลังจากนั้นInitializeComponentให้ลงทะเบียนสำหรับข้อความนั้นดังนี้:

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

คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับชุดเครื่องมือ MVVMLight ได้ที่นี่: ชุดเครื่องมือ MVVMLight บน Codeplex

โปรดสังเกตว่า MVVM ไม่มี "no code-behind at all rule" และคุณสามารถลงทะเบียนสำหรับข้อความในมุมมองโค้ดด้านหลังได้


0

เป็นเรื่องง่าย คุณสามารถสร้างคลาส ViewModel ของคุณเองสำหรับเข้าสู่ระบบ - LoginViewModel คุณสามารถสร้างกล่องโต้ตอบดู var = UserView ใหม่ (); ภายใน LoginViewModel ของคุณ และคุณสามารถตั้งค่าปุ่มเข้าสู่ระบบคำสั่งเข้าสู่ระบบ

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

และ

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

คลาส 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
ใช่มันเป็นวิธีแก้ปัญหาที่ถูกต้อง แต่ถ้าคุณต้องการยึดติดกับ MVVM และการแยกส่วนของ VM และมุมมองคุณจะทำลายรูปแบบ
DHN

สวัสดี @misak - เมื่อพยายามใช้โซลูชันของคุณ (เช่นเดียวกับคำตอบอื่น ๆ ) มันจะObject reference not set to an instance of an object.ทำให้เมธอด CloseLoginView ข้อเสนอแนะวิธีแก้ปัญหานั้นหรือไม่?
WPFNoob

@WPFNoob - ฉันใส่วิธีแก้ปัญหานี้อีกครั้ง ตัวอย่างทำงานได้อย่างถูกต้อง คุณต้องการส่งโซลูชัน Visual Studio ที่สมบูรณ์ทางอีเมลหรือไม่?
misak

@WPFNoob - ฉันเห็นปัญหา คุณกำลังสร้างอินสแตนซ์เป็นกล่องโต้ตอบ var = UserView ใหม่ (); ล้างคำหลัก var (อินสแตนซ์ท้องถิ่น) เขียนทับอินสแตนซ์ส่วนกลางใน LoginViewModel
misak

0

นี่เป็นวิธีที่ฉันทำได้ง่ายๆ:

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

ฉันไม่เห็นว่ามีอะไรผิดปกติกับคำตอบที่คุณเลือกฉันแค่คิดว่านี่อาจเป็นวิธีที่ง่ายกว่านี้!


8
สิ่งนี้ต้องการให้ ViewModel ของคุณทราบและอ้างอิง View ของคุณ
AndrewS

@AndrewS ทำไมถึงไม่ดี?
thestephenstanton

9
ในการทำตามรูปแบบ MVVM ViewModel ไม่ควรรู้เกี่ยวกับ View
MetalMikester

1
เพื่อขยายประเด็นนี้ประเด็นของ MVVM คือการใช้หน่วยรหัส GUI ของคุณให้ได้มากที่สุดในการทดสอบ มุมมองมีการอ้างอิงจำนวนมากซึ่งทำให้ไม่สามารถทดสอบหน่วยได้ ViewModels ควรเป็นหน่วยที่ทดสอบได้ แต่ถ้าคุณให้การพึ่งพาโดยตรงกับมุมมองนั้นจะไม่เป็นเช่นนั้น
ILMTitan

และหากต้องการขยายสิ่งนี้ให้ดียิ่งขึ้น MVVM ที่เขียนอย่างถูกต้องช่วยให้คุณย้ายโซลูชันไปยังแพลตฟอร์มอื่นได้อย่างง่ายดาย โดยเฉพาะอย่างยิ่งคุณควรสามารถใช้โมเดลมุมมองของคุณซ้ำได้โดยไม่มีการเปลี่ยนแปลงใด ๆ ในกรณีนี้หากคุณย้ายโซลูชันไปที่ Android ก็จะไม่ได้ผลเนื่องจาก Android ไม่มีแนวคิดเรื่อง Window -1 สำหรับโซลูชันทำลาย MVVM
Spook

0

คุณอาจถือว่าหน้าต่างเป็นบริการ (เช่นบริการ UI) และส่งผ่านตัวเองไปยังดูโมเดลผ่านอินเทอร์เฟซเช่น:

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

การแก้ปัญหานี้มี upsides มากที่สุดของการส่งผ่านมุมมองของตัวเองไป ViewModel โดยไม่ต้องมีข้อเสียของการทำลาย MVVM เพราะแม้ว่ามุมมองทางร่างกายจะถูกส่งไป ViewModel IMainWindowAccessหลังยังไม่ทราบเกี่ยวกับอดีตก็เห็นเพียงบางส่วนเท่านั้น ตัวอย่างเช่นหากเราต้องการย้ายโซลูชันนี้ไปยังแพลตฟอร์มอื่นก็เป็นเพียงเรื่องของการนำไปใช้IMainWindowAccessอย่างถูกต้องActivityเท่านั้น

ฉันโพสต์วิธีแก้ปัญหาที่นี่เพื่อเสนอแนวทางที่แตกต่างจากเหตุการณ์ (แม้ว่าจะคล้ายกันมากก็ตาม) เพราะดูเหมือนว่าจะง่ายกว่าเหตุการณ์ที่จะใช้ (การแนบ / การถอด ฯลฯ ) เล็กน้อย แต่ยังคงสอดคล้องกับรูปแบบ MVVM


-1

คุณสามารถปิดหน้าต่างปัจจุบันได้โดยใช้รหัสต่อไปนี้:

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

6
หากคุณมีมากกว่าหนึ่งหน้าต่างอาจเป็นการปิดหน้าต่างที่ไม่ถูกต้อง
Sasha

17
โอ้พระเจ้า! คุณได้สังหาร MVVM
Hossein Shahdoost

-7

System.Environment.Exit (0); ในมุมมองแบบจำลองจะใช้งานได้


6
ไม่เคยชิน มันจะออกจากแอพพลิเคชั่นและไม่ปิดหน้าต่างปัจจุบัน
Tilak

สิ่งนี้ช่วยแก้ปัญหาของฉันได้เพราะปิด mainWindow (ถึงฉัน) == ออกจากแอปพลิเคชัน วิธีการที่เสนอทั้งหมดยกเว้นวิธีนี้มีจุดยุ่งยากเมื่อถูกเรียกจากเธรดที่แตกต่างกัน แต่วิธีนี้ไม่สนใจว่าใครเป็นผู้โทร :) นั่นคือทั้งหมดที่ฉันต้องการ!
Hamed

BuAHahahAHahah ขออภัยไม่สามารถต้านทานได้
L.Trabacchin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.