ViewModel ควรปิดแบบฟอร์มอย่างไร


247

ฉันพยายามที่จะเรียนรู้ WPF และปัญหา MVVM แต่มีอุปสรรค คำถามนี้คล้ายกัน แต่ไม่เหมือนกันกับคำถามนี้ (การจัดการไดอะล็อก -in-wpf-with-mvvm) ...

ฉันมีแบบฟอร์ม "เข้าสู่ระบบ" ที่เขียนโดยใช้รูปแบบ MVVM

แบบฟอร์มนี้มี ViewModel ซึ่งเก็บชื่อผู้ใช้และรหัสผ่านซึ่งถูกผูกไว้กับมุมมองใน XAML โดยใช้การเชื่อมโยงข้อมูลปกติ นอกจากนี้ยังมีคำสั่ง "เข้าสู่ระบบ" ซึ่งถูกผูกไว้กับปุ่ม "เข้าสู่ระบบ" บนแบบฟอร์ม agan ใช้ databinding ปกติ

เมื่อคำสั่ง "ลงชื่อเข้าใช้" เริ่มทำงานจะเรียกใช้ฟังก์ชันใน ViewModel ซึ่งจะดับและส่งข้อมูลผ่านเครือข่ายเพื่อเข้าสู่ระบบเมื่อฟังก์ชันนี้เสร็จสมบูรณ์มีการดำเนินการ 2 รายการ:

  1. การเข้าสู่ระบบไม่ถูกต้อง - เราเพิ่งแสดงกล่องข้อความและทุกอย่างเรียบร้อย

  2. การเข้าสู่ระบบนั้นถูกต้องเราต้องปิดแบบฟอร์มการเข้าสู่ระบบและให้มันกลับมาจริงเพราะDialogResult...

ปัญหาคือ ViewModel ไม่รู้อะไรเกี่ยวกับมุมมองจริงดังนั้นจะปิดมุมมองอย่างไรและบอกให้ส่งคืน DialogResult ที่เฉพาะเจาะจงได้อย่างไร ฉันสามารถติดโค้ดบางอย่างใน CodeBehind และ / หรือส่งมุมมองผ่านไปยัง ViewModel ได้ แต่ดูเหมือนว่ามันจะเอาชนะ MVVM ได้ทั้งหมด ...


ปรับปรุง

ในท้ายที่สุดฉันเพิ่งละเมิด "ความบริสุทธิ์" ของรูปแบบ MVVM และให้มุมมองเผยแพร่ClosedกิจกรรมและเปิดเผยCloseวิธีการ ViewModel view.Closeก็จะเพียงโทร มุมมองเป็นที่รู้จักผ่านทางอินเตอร์เฟสและต่อสายผ่านคอนเทนเนอร์ IOC เท่านั้นดังนั้นจึงไม่มีการทดสอบหรือการบำรุงรักษาที่หายไป

ดูเหมือนว่าค่อนข้างโง่ที่คำตอบที่ยอมรับคือ -5 คะแนน! ในขณะที่ฉันตระหนักดีถึงความรู้สึกที่ดีที่เราได้รับจากการแก้ปัญหาในขณะที่ "บริสุทธิ์" แน่นอนฉันไม่ใช่คนเดียวที่คิดว่าเหตุการณ์ 200 บรรทัดคำสั่งและพฤติกรรมเพียงเพื่อหลีกเลี่ยงวิธีการหนึ่งบรรทัดใน ชื่อของ "pattern" และ "purity" นั้นค่อนข้างไร้สาระ ....


2
ฉันไม่ได้ downvote คำตอบที่ยอมรับ แต่ฉันเดาว่าเหตุผลสำหรับ downvote นั้นคือมันไม่ได้มีประโยชน์โดยทั่วไปแม้ว่ามันจะใช้ได้ในกรณีเดียวก็ตาม คุณพูดด้วยตัวคุณเองในความคิดเห็นอื่น: "ในขณะที่แบบฟอร์มการเข้าสู่ระบบเป็นกล่องโต้ตอบ 'สองฟิลด์' ฉันมีอีกหลายคนที่มีความซับซ้อนมากขึ้น (ดังนั้นจึงรับประกัน MVVM) แต่ยังคงต้องปิด ... "
Joe สีขาว

1
ฉันเห็นประเด็นของคุณ แต่โดยส่วนตัวฉันคิดว่าแม้ในกรณีทั่วไปCloseวิธีการง่าย ๆยังคงเป็นวิธีที่ดีที่สุด ทุกสิ่งทุกอย่างในกล่องโต้ตอบที่ซับซ้อนกว่านี้คือ MVVM และ databound แต่ดูเหมือนว่าโง่ที่จะใช้ "การแก้ปัญหา" ขนาดใหญ่ที่นี่แทนที่จะเป็นวิธีการง่ายๆ ...
Orion Edwards

2
คุณสามารถตรวจสอบลิงค์ต่อไปนี้เพื่อดูผลการโต้ตอบasimsajjad.blogspot.com/2010/10/…ซึ่งจะคืนค่าการโต้ตอบไดอะล็อกและปิดมุมมองจาก viewModel
Asim Sajjad

3
โปรดเปลี่ยนคำตอบที่ยอมรับสำหรับคำถามนี้ มีวิธีแก้ปัญหาที่ดีมากมายที่ดีกว่าใครซักคนที่ใช้ MVVM สำหรับฟังก์ชั่นนี้ นั่นไม่ใช่คำตอบนั่นคือการหลีกเลี่ยง
ScottCher

2
@OrionEdwards ฉันคิดว่าคุณมีสิทธิ์ที่จะทำลายรูปแบบที่นี่ วัตถุประสงค์หลักของรูปแบบการออกแบบคือการเร่งรอบการพัฒนาเพิ่มความสามารถในการบำรุงรักษาและทำให้รหัสของคุณง่ายขึ้นโดยการทำให้ทีมงานทั้งหมดปฏิบัติตามกฎเดียวกัน สิ่งนี้ไม่สามารถทำได้โดยการเพิ่มการพึ่งพาไลบรารีภายนอกและใช้โค้ดหลายร้อยบรรทัดเพื่อทำงานให้สำเร็จโดยไม่สนใจว่ามีวิธีแก้ปัญหาที่ง่ายกว่ามากเพียงเพราะพวกเขาดื้อรั้นที่จะเสียสละ "ความบริสุทธิ์" ของรูปแบบ เพียงให้แน่ใจว่าเอกสารสิ่งที่ทำและ your've KISSรหัสของคุณ ( k EEP ฉันทีs Hort และs Imple)
M463

คำตอบ:


324

ฉันได้แรงบันดาลใจจากคำตอบของ Thejuan ในการเขียนอสังหาริมทรัพย์ที่แนบมาเรียบง่าย ไม่มีสไตล์ไม่มีทริกเกอร์; คุณสามารถทำสิ่งนี้แทน:

<Window ...
        xmlns:xc="clr-namespace:ExCastle.Wpf"
        xc:DialogCloser.DialogResult="{Binding DialogResult}">

สิ่งนี้เกือบจะสะอาดราวกับว่าทีม WPF ได้ถูกต้องแล้วและทำให้ DialogResult เป็นทรัพย์สินที่ต้องพึ่งพาตั้งแต่แรก เพียงแค่ใส่bool? DialogResultคุณสมบัติบน ViewModel ของคุณและใช้ INotifyPropertyChanged และvoilà ViewModel ของคุณสามารถปิดหน้าต่าง (และตั้งค่า DialogResult) เพียงแค่ตั้งค่าคุณสมบัติ MVVM ตามที่ควรจะเป็น

นี่คือรหัสสำหรับ DialogCloser:

using System.Windows;

namespace ExCastle.Wpf
{
    public static class DialogCloser
    {
        public static readonly DependencyProperty DialogResultProperty =
            DependencyProperty.RegisterAttached(
                "DialogResult",
                typeof(bool?),
                typeof(DialogCloser),
                new PropertyMetadata(DialogResultChanged));

        private static void DialogResultChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var window = d as Window;
            if (window != null)
                window.DialogResult = e.NewValue as bool?;
        }
        public static void SetDialogResult(Window target, bool? value)
        {
            target.SetValue(DialogResultProperty, value);
        }
    }
}

ฉันโพสต์สิ่งนี้บนบล็อกของฉันด้วย


3
อันนี้เป็นคำตอบที่ฉันชอบมากที่สุด! เขียนงานที่ดีที่แนบคุณสมบัติ
Jorge Vargas

2
ตัวเลือกที่ดี แต่มีข้อบกพร่องเล็กน้อยในการแก้ปัญหานี้ ถ้ารูปแบบการดูสำหรับไดอะล็อกเป็นแบบซิงเกิลค่า DialogResult จะถูกยกยอดไปข้างหน้าเพื่อใช้งานไดอะล็อกครั้งถัดไป นั่นหมายความว่ามันจะยกเลิกหรือยอมรับทันทีก่อนที่จะแสดงตัวเองดังนั้นกล่องโต้ตอบจะไม่แสดงเป็นครั้งที่สอง
Gone Coding

13
@HiTech Magic ดูเหมือนว่าบั๊กจะใช้ ViewModel แบบซิงเกิลในตอนแรก (ยิ้ม) อย่างจริงจังทำไมบนโลกนี้ถึงอยากให้รุ่นวิวเวอร์ซิงเกิล? มันเป็นความคิดที่ดีที่จะรักษาสถานะที่ไม่แน่นอนในตัวแปรส่วนกลาง ทำการทดสอบฝันร้ายและการทดสอบเป็นหนึ่งในเหตุผลที่คุณควรใช้ MVVM ตั้งแต่แรก
Joe White

3
MVVM ไม่ใช่ประเด็นที่จะจับคู่ตรรกะของคุณเข้ากับ UI ที่เฉพาะเจาะจงหรือไม่? ในกรณีนี้บูล? แน่นอนที่สุดไม่สามารถใช้งานได้โดย UI อื่นเช่น WinForm และ DialogCloser นั้นใช้เฉพาะกับ WPF ดังนั้นวิธีนี้จะพอดีกับวิธีการแก้ปัญหา? นอกจากนี้ทำไมต้องเขียนรหัส 2x-10x เพียงเพื่อปิดหน้าต่างผ่านทางการเชื่อม?
David Anderson

2
@ DavidAnderson ฉันจะไม่ลอง MVVM กับ WinForms ไม่ว่าในกรณีใด การรองรับฐานข้อมูลของมันนั้นอ่อนแอเกินไปและ MVVM ต้องอาศัยระบบการเชื่อมต่อที่ดี และมันก็ไม่มีที่ไหนใกล้กับรหัส 2x-10x คุณเขียนโค้ดนั้นหนึ่งครั้งไม่ใช่หนึ่งครั้งสำหรับทุกหน้าต่าง หลังจากนั้นเป็นการรวมหนึ่งบรรทัดพร้อมคุณสมบัติการแจ้งเตือนโดยใช้กลไกเดียวกับที่คุณใช้อยู่แล้วสำหรับทุกอย่างในมุมมองของคุณ (เช่นคุณไม่จำเป็นต้องเพิ่มส่วนต่อประสานมุมมองพิเศษเพื่อจัดการกับการปิด หน้าต่าง). คุณยินดีที่จะทำการแลกเปลี่ยนอื่น ๆ แต่ดูเหมือนว่าเป็นข้อตกลงที่ดีสำหรับฉัน
Joe White

64

จากมุมมองของฉันคำถามนี้ค่อนข้างดีเพราะใช้วิธีการเดียวกันไม่เพียง แต่สำหรับหน้าต่าง "เข้าสู่ระบบ" แต่สำหรับหน้าต่างทุกประเภท ฉันได้ตรวจสอบคำแนะนำมากมายและไม่มีอะไรดีสำหรับฉัน โปรดตรวจสอบคำแนะนำของฉันที่ถูกนำมาจากบทความ MVVM รูปแบบการออกแบบ

แต่ละคลาส ViewModel ควรสืบทอดจากWorkspaceViewModelที่มีRequestCloseเหตุการณ์และCloseCommandคุณสมบัติของICommandประเภท การใช้งานเริ่มต้นของCloseCommandคุณสมบัติจะเพิ่มRequestCloseเหตุการณ์

เพื่อปิดหน้าต่างOnLoadedวิธีการในหน้าต่างของคุณควรจะถูกแทนที่:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

หรือOnStartupวิธีการตรวจสอบของคุณ:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

ฉันเดาว่าRequestCloseเหตุการณ์และCloseCommandการใช้งานคุณสมบัติในWorkspaceViewModelค่อนข้างชัดเจน แต่ฉันจะแสดงให้พวกเขามีความสอดคล้อง:

public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose != null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

และรหัสแหล่งที่มาของRelayCommand:

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

ป.ล.อย่าปฏิบัติกับแหล่งที่มาเหล่านั้นอย่างเลวร้าย! ถ้าฉันมีพวกเขาเมื่อวานนี้ซึ่งจะช่วยฉันสองสามชั่วโมง ...

PPSความคิดเห็นหรือข้อเสนอแนะยินดีต้อนรับ


2
อืมข้อเท็จจริงที่ว่าคุณติดเข้ากับตัวจัดการเหตุการณ์customer.RequestCloseในโค้ดด้านหลังของไฟล์ XAML ของคุณไม่ได้ละเมิดรูปแบบ MVVM ใช่หรือไม่ คุณสามารถผูกกับClickตัวจัดการเหตุการณ์ด้วยปุ่มปิดของคุณในครั้งแรกที่เห็นว่าคุณได้สัมผัสโค้ดข้างหลังแล้วและทำthis.Close()! ขวา?
GONeale

1
ฉันไม่ได้มีปัญหามากเกินไปกับวิธีการจัดกิจกรรม แต่ฉันไม่ชอบคำว่า RequestClose เพราะสำหรับฉันแล้วมันยังหมายถึงความรู้มากมายเกี่ยวกับการใช้งานมุมมอง ฉันชอบที่จะเปิดเผยคุณสมบัติเช่น IsCancelled ซึ่งมีแนวโน้มที่จะมีความหมายมากกว่าในบริบทและบ่งบอกถึงมุมมองที่ควรทำในการตอบสนองน้อยลง
jpierson

18

ฉันใช้พฤติกรรมที่แนบมาเพื่อปิดหน้าต่าง ผูกคุณสมบัติ "สัญญาณ" ใน ViewModel ของคุณกับพฤติกรรมที่แนบมา (จริง ๆ แล้วฉันใช้ทริกเกอร์) เมื่อตั้งค่าเป็นจริงพฤติกรรมจะปิดหน้าต่าง

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/


นี่เป็นคำตอบเดียวเท่านั้นที่ไม่ต้องการโค้ดด้านหลังในหน้าต่าง (และปิดหน้าต่าง modal จริง ๆ แทนที่จะแนะนำวิธีอื่น) น่าเสียดายที่มันต้องมีความซับซ้อนมากด้วย Style and Trigger และ muck ทั้งหมด - ดูเหมือนว่านี่น่าจะเป็นไปได้ที่จะมีพฤติกรรมที่แนบมาด้วยบรรทัดเดียว
Joe White

4
ตอนนี้มันเป็นไปได้ด้วยพฤติกรรมที่แนบหนึ่งบรรทัด ดูคำตอบของฉัน: stackoverflow.com/questions/501886/…
Joe White

15

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

ที่กล่าวว่า .. ฉันคิดว่ากรณีของคุณอาจเหมาะสมกับการปรับเปลี่ยนเล็กน้อย

ในกรณีส่วนใหญ่ฉันเจอ WPF ช่วยให้คุณได้โดยไม่ต้องหลายWindows บางทีคุณอาจลองใช้Frames และPages แทน Windows ด้วยDialogResults

ในกรณีของคุณคำแนะนำของฉันจะมีการLoginFormViewModelจัดการLoginCommandและถ้าเข้าสู่ระบบไม่ถูกต้องตั้งค่าคุณสมบัติLoginFormViewModelเพื่อเป็นค่าที่เหมาะสม ( falseหรือบางค่า enum เช่นUserAuthenticationStates.FailedAuthentication) คุณจะทำเช่นเดียวกันสำหรับการเข้าสู่ระบบที่ประสบความสำเร็จ ( trueหรือค่า enum อื่น ๆ ) แล้วคุณต้องการใช้DataTriggerที่ตอบสนองต่อการตรวจสอบผู้ใช้รัฐต่างๆและสามารถใช้ง่ายSetterที่จะเปลี่ยนคุณสมบัติของ SourceFrame

มีหน้าต่างเข้าสู่ระบบของคุณกลับDialogResultฉันคิดว่าเป็นที่ที่คุณสับสน นั่นDialogResultเป็นทรัพย์สินของ ViewModel ของคุณ ในประสบการณ์ของฉันที่เป็นที่ยอมรับอย่าง จำกัด กับ WPF เมื่อบางสิ่งบางอย่างไม่ปกติเพราะฉันคิดในแง่ของวิธีที่ฉันจะทำสิ่งเดียวกันใน WinForms

หวังว่าจะช่วย


10

สมมติว่ากล่องโต้ตอบการเข้าสู่ระบบของคุณเป็นหน้าต่างแรกที่สร้างขึ้นให้ลองทำสิ่งนี้ภายในคลาส LoginViewModel ของคุณ:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

ผู้ชายนี้ง่ายและใช้งานได้ดี ขณะนี้ฉันใช้วิธีนี้
Erre Efe

มันทำงานได้เฉพาะกับหน้าต่างหลัก ดังนั้นอย่าใช้มันสำหรับหน้าต่างอื่น ๆ
Oleksii

7

นี่เป็นวิธีที่ง่ายและสะอาด - คุณเพิ่มเหตุการณ์ลงใน ViewModel และสั่งให้หน้าต่างปิดตัวเองเมื่อเหตุการณ์นั้นเริ่มทำงาน

สำหรับรายละเอียดเพิ่มเติมดูโพสต์บล็อกของฉันปิดหน้าต่างจาก ViewModel

XAML:

<Window
  x:Name="this"
  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding}" EventName="Closed">
      <ei:CallMethodAction
        TargetObject="{Binding ElementName=this}"
        MethodName="Close"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
<Window>

ViewModel:

private ICommand _SaveAndCloseCommand;
public ICommand SaveAndCloseCommand
{
  get
  {
    return _SaveAndCloseCommand ??
      (_SaveAndCloseCommand = new DelegateCommand(SaveAndClose));
  }
}
private void SaveAndClose()
{
  Save();
  Close();
}

public event EventHandler Closed;
private void Close()
{
  if (Closed != null) Closed(this, EventArgs.Empty);
}

หมายเหตุ: ตัวอย่างใช้ Prism's DelegateCommand(ดูPrism: Commanding ) แต่ICommandการใช้งานใด ๆสามารถนำไปใช้กับเรื่องนั้นได้

คุณสามารถใช้พฤติกรรมจากนี้แพคเกจอย่างเป็นทางการ


2
+1 แต่คุณควรให้รายละเอียดเพิ่มเติมในคำตอบของตัวเองตัวอย่างเช่นโซลูชันนี้ต้องการการอ้างอิงถึงชุดประกอบการโต้ตอบ Expression Blend
ท่อง

6

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


2
นั่นคือสิ่งที่ฉันมักจะทำเช่นกัน แม้ว่ามันจะดูสกปรกไปหน่อยเมื่อพิจารณาถึงสิ่งที่ควบคุมด้วยคำสั่ง wpf ใหม่
Botz3000

4

นี่คือสิ่งที่ฉันทำในตอนแรกซึ่งใช้งานได้ แต่ดูเหมือนว่าจะค่อนข้างยืดยาวและน่าเกลียด

1: App.xaml.cs

public partial class App : Application
{
    // create a new global custom WPF Command
    public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}

2: LoginForm.xaml

// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />

3: LoginForm.xaml.cs

// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
    DialogResult = true;
    Close();
}

ที่ 4: LoginFormViewModel.cs

// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
    App.LoggedIn.Execute(this, null);
}

ฉันในภายหลังแล้วลบรหัสนี้ทั้งหมดและเพิ่งมีการLoginFormViewModelเรียกวิธีการปิดในมุมมองของมัน มันจบลงด้วยการที่ดีกว่าและง่ายต่อการติดตาม IMHO จุดของรูปแบบคือการให้คนเป็นวิธีที่ง่ายต่อการเข้าใจสิ่งที่ app ของคุณจะทำและในกรณีนี้ MVVM ก็ทำให้มันไกลยากที่จะเข้าใจกว่าถ้าผมไม่ได้ใช้มันและตอนนี้ต่อต้าน -pattern


3

FYI ฉันพบปัญหาเดียวกันนี้และฉันคิดว่าฉันคิดหาวิธีแก้ไขที่ไม่ต้องการกลมหรือสถิตแม้ว่ามันอาจไม่ใช่คำตอบที่ดีที่สุด ฉันปล่อยให้พวกคุณตัดสินใจเอง

ในกรณีของฉัน ViewModel ที่ทำให้หน้าต่างแสดงผลทันที (เรียกว่า ViewModelMain) ก็รู้เกี่ยวกับ LoginFormViewModel (โดยใช้สถานการณ์ข้างต้นเป็นตัวอย่าง)

ดังนั้นสิ่งที่ฉันทำคือการสร้างสถานที่ให้บริการใน LoginFormViewModel ที่เป็นประเภท ICommand (ให้เรียกว่า CloseWindowCommand) จากนั้นก่อนที่ฉันจะเรียกใช้. ShowDialog () บนหน้าต่างฉันจะตั้งค่าคุณสมบัติ CloseWindowCommand บน LoginFormViewModel เป็นวิธี window.Close () ของหน้าต่างที่ฉันสร้างอินสแตนซ์ จากนั้นภายใน LoginFormViewModel ทั้งหมดที่ฉันต้องทำคือโทร CloseWindowCommand.Execute () เพื่อปิดหน้าต่าง

มันเป็นบิตของวิธีแก้ปัญหา / แฮ็คฉันคิดว่า แต่มันทำงานได้ดีโดยไม่ทำลายรูปแบบ MVVM จริงๆ

สามารถวิพากษ์วิจารณ์กระบวนการนี้ได้มากเท่าที่คุณต้องการฉันสามารถรับมันได้! :)


ฉันไม่แน่ใจว่าฉันคร่ำครวญอย่างเต็มที่ แต่นี่ไม่ได้หมายความว่า MainWindow ของคุณจะต้องมีการยกตัวอย่างก่อนที่จะเข้าสู่ระบบ Windows ของคุณ? นั่นคือสิ่งที่ฉันต้องการที่จะหลีกเลี่ยงถ้าเป็นไปได้
Orion เอ็ดเวิร์ด

3

นี่อาจจะช้ามาก แต่ฉันเจอปัญหาเดียวกันและพบวิธีแก้ปัญหาที่เหมาะกับฉัน

ฉันไม่สามารถหาวิธีสร้างแอปได้โดยไม่ต้องมีกล่องโต้ตอบ (อาจเป็นเพียงบล็อกความคิด) ดังนั้นฉันจึงรู้สึกอึดอัดใจกับ MVVM และแสดงบทสนทนา ดังนั้นฉันจึงเจอบทความ CodeProject นี้:

http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

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

คุณสามารถตั้งค่าสไตล์ตามปกติใน resourcedictionary ที่โดยทั่วไปจะแสดงกล่องโต้ตอบเมื่อใดก็ตามที่คุณสมบัติเนื้อหาของการควบคุม! = null ผ่านทริกเกอร์:

<Style TargetType="{x:Type d:Dialog}">
    <Style.Triggers>
        <Trigger Property="HasContent"  Value="True">
            <Setter Property="Showing" Value="True" />
        </Trigger>
    </Style.Triggers>
</Style>

ในมุมมองที่คุณต้องการแสดงไดอะล็อกให้มี:

<d:Dialog Content="{Binding Path=DialogViewModel}"/>

และใน ViewModel ของคุณสิ่งที่คุณต้องทำคือตั้งค่าคุณสมบัติเป็นค่า (หมายเหตุ: คลาส ViewModel ต้องสนับสนุน INotifyPropertyChanged เพื่อให้มุมมองรู้ว่ามีอะไรเกิดขึ้น)

ชอบมาก:

DialogViewModel = new DisplayViewModel();

เพื่อให้ตรงกับ ViewModel กับมุมมองคุณควรมีสิ่งนี้ในพจนานุกรมทรัพยากร:

<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
    <vw:DisplayView/>
</DataTemplate>

ด้วยทั้งหมดที่คุณได้รับรหัสหนึ่งซับเพื่อแสดงกล่องโต้ตอบ ปัญหาที่คุณได้รับคือคุณไม่สามารถปิดกล่องโต้ตอบด้วยรหัสด้านบนได้ นั่นคือเหตุผลที่คุณต้องใส่เหตุการณ์ในคลาสพื้นฐานของ ViewModel ซึ่ง DisplayViewModel สืบทอดมาจากและแทนที่จะเป็นรหัสด้านบนเขียนสิ่งนี้

        var vm = new DisplayViewModel();
        vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
        DialogViewModel = vm;

จากนั้นคุณสามารถจัดการกับผลลัพธ์ของไดอะล็อกผ่านการเรียกกลับ

นี่อาจดูซับซ้อนเล็กน้อย แต่เมื่อวางรากฐานแล้วมันก็ค่อนข้างตรงไปตรงมา นี่คือการนำฉันไปใช้ฉันแน่ใจว่ามีคนอื่นอีก :)

หวังว่านี่จะช่วยได้มันช่วยฉันได้


3

ตกลงดังนั้นคำถามนี้เกือบ 6 ปีและฉันยังไม่พบที่นี่สิ่งที่ฉันคิดว่ามันเป็นคำตอบที่เหมาะสมดังนั้นให้ฉันแบ่งปัน "2 เซนต์" ของฉัน ...

จริง ๆ แล้วฉันมีวิธีการทำ 2 วิธีวิธีแรกคือวิธีที่ง่าย ... วิธีที่สองทางขวาดังนั้นหากคุณกำลังมองหาวิธีที่ถูกต้องเพียงข้าม # 1 และข้ามไปที่ # 2 :

1. รวดเร็วและง่าย (แต่ไม่สมบูรณ์)

ถ้าฉันมีเพียงโครงการเล็ก ๆ ฉันก็แค่สร้างCloseWindowActionใน ViewModel:

        public Action CloseWindow { get; set; } // In MyViewModel.cs

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

(โปรดจำไว้ว่า MVVM เกี่ยวกับการแยกมุมมองและ ViewModel ... โค้ดของมุมมองยังคงเป็นมุมมองและตราบใดที่มีการแยกที่เหมาะสมคุณไม่ได้ละเมิดรูปแบบ)

ถ้า ViewModel บางรุ่นสร้างหน้าต่างใหม่:

private void CreateNewView()
{
    MyView window = new MyView();
    window.DataContext = new MyViewModel
                             {
                                 CloseWindow = window.Close,
                             }; 
    window.ShowDialog();
}

หรือถ้าคุณต้องการมันในหน้าต่างหลักของคุณเพียงวางไว้ใต้คอนสตรัคเตอร์ของ View:

public MyView()
{
    InitializeComponent();           
    this.DataContext = new MainViewModel
                           {
                                CloseWindow = this.Close
                           };
}

เมื่อคุณต้องการปิดหน้าต่างเพียงแค่เรียกการกระทำบน ViewModel ของคุณ


2. วิธีการที่เหมาะสม

ตอนนี้วิธีการที่เหมาะสมในการทำจะใช้ปริซึม (IMHO) และทั้งหมดเกี่ยวกับการที่จะสามารถพบได้ที่นี่

คุณสามารถทำให้การขอปฏิสัมพันธ์ , เติมมันมีข้อมูลอะไรก็ตามที่คุณจะต้องในหน้าต่างใหม่ของคุณ, อาหารกลางวันมันใกล้มันและแม้จะได้รับข้อมูลกลับ ทั้งหมดนี้สรุปและได้รับการอนุมัติ MVVM คุณยังจะได้รับสถานะของวิธีหน้าต่างถูกปิดเช่นถ้าผู้ใช้CanceledหรือAccepted(ปุ่ม OK) หน้าต่างและข้อมูลสำรองถ้าคุณต้องการมัน มันค่อนข้างซับซ้อนและมีคำตอบ # 1 แต่มันก็มีความสมบูรณ์มากกว่าและรูปแบบที่แนะนำโดย Microsoft

ลิงก์ที่ฉันให้มีตัวอย่างโค้ดและตัวอย่างทั้งหมดดังนั้นฉันจะไม่สนใจที่จะวางโค้ดใด ๆ ในที่นี่เพียงอ่านบทความของการดาวน์โหลด Prism Quick Start และเรียกใช้มันง่ายมากที่จะเข้าใจ verbose อีกเล็กน้อย ทำให้มันใช้งานได้ แต่ข้อดีนั้นใหญ่กว่าเพียงแค่ปิดหน้าต่าง


เป็นวิธีที่ดี แต่ความละเอียดและการกำหนดของ ViewModels ไม่สามารถเป็นไปข้างหน้าได้เสมอ จะเกิดอะไรขึ้นถ้ามุมมองโมเดลเดียวกันคือ DataContext ของ Windows หลาย ๆ ตัว
Kylo Ren

จากนั้นฉันเดาว่าคุณจะต้องปิดหน้าต่างทั้งหมดในครั้งเดียวจำไว้ว่าการกระทำสามารถกระตุ้นผู้ได้รับมอบหมายหลายคนพร้อมกันเพียงแค่ใช้+=เพื่อเพิ่มผู้ได้รับมอบหมายและเรียกการกระทำมันจะเป็นการเปิดไฟทั้งหมดของพวกเขา ต้องสร้างตรรกะพิเศษบน VM ของคุณดังนั้นมันจะทราบว่าหน้าต่างใดที่จะปิด (อาจมีชุดของการดำเนินการปิด) .... แต่ฉันคิดว่าการมีหลายมุมมองที่ผูกกับ VM หนึ่งไม่ใช่วิธีปฏิบัติที่ดีที่สุด ดีกว่าที่จะจัดการให้มีหนึ่งมุมมองและหนึ่งอินสแตนซ์ VM ผูกไว้กับแต่ละอื่น ๆ และอาจจะเป็นผู้ปกครอง VM ที่จัดการเด็ก ๆ ทั้งหมด VM ที่ถูกผูกไว้กับมุมมองทั้งหมด
mFeinstein

3
public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}

2

คุณสามารถให้ ViewModel เปิดเผยเหตุการณ์ที่ View register จากนั้นเมื่อ ViewModel ตัดสินใจเวลาที่จะปิดมุมมองมันจะยิงเหตุการณ์ที่ทำให้มุมมองปิด หากคุณต้องการส่งคืนค่าผลลัพธ์ที่เฉพาะเจาะจงคุณจะมีคุณสมบัติใน ViewModel สำหรับสิ่งนั้น


ฉันเห็นด้วยกับสิ่งนี้ - ความเรียบง่ายมีค่า ฉันต้องคิดว่าจะเกิดอะไรขึ้นเมื่อนักพัฒนารุ่นน้องคนต่อไปได้รับการว่าจ้างให้เข้าร่วมโครงการนี้ ฉันเดาว่าเขาจะมีโอกาสที่ดีกว่านี้มากในการทำให้ถูกต้องตามที่คุณอธิบาย ถ้าคุณไม่คิดว่าคุณจะรักษารหัสนี้ไว้ตลอดไป? +1
ดีน

2

เพียงเพิ่มคำตอบจำนวนมากฉันต้องการเพิ่มต่อไปนี้ สมมติว่าคุณมี ICommand บน ViewModel ของคุณและคุณต้องการให้คำสั่งปิดหน้าต่าง (หรือการดำเนินการอื่น ๆ สำหรับเรื่องนั้น) คุณสามารถใช้สิ่งต่อไปนี้

var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
    if (windows[i].DataContext == this)
        windows[i].Close();

มันไม่ได้สมบูรณ์แบบและอาจทดสอบได้ยาก (เนื่องจากเป็นการยากที่จะเยาะเย้ย / ไม่ขยับเขยื้อน) แต่มันก็สะอาดกว่า (IMHO) มากกว่าวิธีอื่น ๆ

เอริค


ฉันมีความสุขมากเมื่อเห็นคำตอบง่ายๆของคุณ! แต่มันก็ไม่ทำงานเหมือนกัน! ฉันต้องเปิดและปิดด้วย Visual Basic คุณรู้ถึงความเท่าเทียมของ (windows [i] .DataContext == this) ใน VB หรือไม่
Ehsan

ฉันได้รับมันในที่สุด! :) ขอบคุณ ถ้า windows (i) .DataContext คือฉัน
Ehsan

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

1

ฉันใช้วิธีการแก้ปัญหาของ Joe White แต่พบปัญหากับ " DialogResultเป็นครั้งคราวสามารถตั้งค่าได้หลังจากข้อผิดพลาดWindow ถูกสร้างและแสดงเป็นไดอะล็อก "

ฉันเก็บ ViewModel ไว้รอบ ๆ หลังจากที่ View ถูกปิดและบางครั้งฉันก็เปิดมุมมองใหม่โดยใช้ VM เดียวกัน ดูเหมือนว่าการปิดมุมมองใหม่ก่อนที่มุมมองเก่า ๆ จะถูกรวบรวมขยะทำให้DialogResultChangedพยายามตั้งDialogResultคุณสมบัติบนหน้าต่างปิดดังนั้นจึงกระตุ้นให้เกิดข้อผิดพลาด

วิธีแก้ไขของฉันคือเปลี่ยนDialogResultChangedเพื่อตรวจสอบคุณสมบัติIsLoadedของหน้าต่าง:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

หลังจากทำการเปลี่ยนแปลงนี้สิ่งที่แนบมากับกล่องโต้ตอบปิดจะถูกละเว้น


ขอบคุณครับ ฉันมีปัญหาเดียวกัน
DJ Burb

1

ฉันลงเอยผสมคำตอบของ Joe Whiteและรหัสจากคำตอบของ Adam Millsเนื่องจากฉันต้องการแสดงการควบคุมผู้ใช้ในหน้าต่างที่สร้างขึ้นโดยทางโปรแกรม ดังนั้น DialogCloser ไม่จำเป็นต้องอยู่บนหน้าต่างมันสามารถอยู่ในการควบคุมของผู้ใช้เอง

<UserControl ...
    xmlns:xw="clr-namespace:Wpf"
    xw:DialogCloser.DialogResult="{Binding DialogResult}">

และ DialogCloser จะค้นหาหน้าต่างของการควบคุมผู้ใช้หากไม่ได้เชื่อมต่อกับหน้าต่างเอง

namespace Wpf
{
  public static class DialogCloser
  {
    public static readonly DependencyProperty DialogResultProperty =
        DependencyProperty.RegisterAttached(
            "DialogResult",
            typeof(bool?),
            typeof(DialogCloser),
            new PropertyMetadata(DialogResultChanged));

    private static void DialogResultChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
      var window = d.GetWindow();
      if (window != null)
        window.DialogResult = e.NewValue as bool?;
    }

    public static void SetDialogResult(DependencyObject target, bool? value)
    {
      target.SetValue(DialogResultProperty, value);
    }
  }

  public static class Extensions
  {
    public static Window GetWindow(this DependencyObject sender_)
    {
      Window window = sender_ as Window;        
      return window ?? Window.GetWindow( sender_ );
    }
  }
}

1

พฤติกรรมเป็นวิธีที่สะดวกที่สุดที่นี่

  • จากมือข้างหนึ่งมันสามารถผูกเข้ากับ viewmodel ที่กำหนด (ที่สามารถส่งสัญญาณ "ปิดแบบฟอร์ม!")

  • จากอีกทางหนึ่งมันสามารถเข้าถึงฟอร์มเองได้ดังนั้นจึงสามารถสมัครรับข้อมูลเหตุการณ์เฉพาะฟอร์มที่จำเป็นหรือแสดงข้อความยืนยันหรือสิ่งอื่น ๆ

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


0

ทำไมไม่เพียงส่งหน้าต่างเป็นพารามิเตอร์คำสั่ง?

ค#:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />

ฉันไม่คิดว่าเป็นความคิดที่ดีที่จะ จำกัด VM ไว้ที่ประเภท Window
Shimmy Weitzhandler

2
ฉันไม่คิดว่าเป็นความคิดที่ดีที่จะ จำกัด VM ให้เป็นWindowประเภทที่ค่อนข้าง "บริสุทธิ์" MVVM ดูคำตอบนี้โดยที่ VM ไม่ได้ถูก จำกัด ไว้ที่Windowวัตถุ
Shimmy Weitzhandler

วิธีนี้การพึ่งพาอาศัยกันจะถูกวางไว้บนปุ่มซึ่งแน่นอนไม่สามารถเป็นสถานการณ์เสมอ นอกจากนี้การส่งประเภท UI ไปยัง ViewModel ก็เป็นวิธีปฏิบัติที่ไม่ดีเช่นกัน
Kylo Ren

0

อีกวิธีหนึ่งคือการสร้างคุณสมบัติด้วย INotifyPropertyChanged ในมุมมองแบบจำลองเช่น DialogResult จากนั้นใน Code Behind เขียนสิ่งนี้:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

_someViewModel_PropertyChangedส่วนที่สำคัญที่สุดคือ DialogResultPropertyNameสามารถบางสตริง const SomeViewModelในที่สาธารณะ

ฉันใช้กลอุบายชนิดนี้เพื่อทำการเปลี่ยนแปลงบางอย่างในการควบคุมมุมมองในกรณีที่การดำเนินการใน ViewModel ทำได้ยาก OnPropertyChanged ใน ViewModel คุณสามารถทำอะไรก็ได้ที่คุณต้องการใน View ViewModel ยังคงเป็น 'หน่วยที่ทดสอบได้' และโค้ดบางบรรทัดในโค้ดด้านหลังไม่สร้างความแตกต่าง


0

ฉันจะไปทางนี้:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

0

ฉันได้อ่านคำตอบทั้งหมด แต่ต้องบอกว่าส่วนใหญ่ไม่ดีพอหรือแย่กว่านั้น

คุณสามารถจัดการกับสิ่งนี้ได้อย่างงดงามด้วยคลาสDialogServiceซึ่งความรับผิดชอบคือการแสดงหน้าต่างข้อความและผลการโต้ตอบกลับมา ฉันได้สร้างตัวอย่างโครงการที่แสดงว่ามีการนำไปใช้งานและการใช้

นี่คือส่วนที่สำคัญที่สุด:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

มันไม่ง่ายกว่านี้เหรอ? อึดอัดใจมากขึ้นอ่านได้ง่ายขึ้นและสุดท้าย แต่ไม่น้อยกว่าการดีบักได้ง่ายกว่า EventAggregator หรือวิธีแก้ไขปัญหาอื่นที่คล้ายคลึงกัน?

อย่างที่คุณเห็นในมุมมองโมเดลของฉันฉันใช้วิธีแรกของ ViewModel ที่อธิบายไว้ในโพสต์ของฉันที่นี่: วิธีปฏิบัติที่ดีที่สุดสำหรับการเรียกดูจาก ViewModel ใน WPF

แน่นอนว่าในโลกแห่งความเป็นจริงDialogService.ShowDialogต้องมีตัวเลือกเพิ่มเติมในการกำหนดค่าไดอะล็อกเช่นปุ่มและคำสั่งที่ควรปฏิบัติ มีวิธีที่แตกต่างในการทำเช่นนั้น แต่มันอยู่นอกขอบเขต :)


0

แม้ว่าสิ่งนี้จะไม่ตอบคำถามว่าจะทำอย่างไรผ่าน viewmodel แต่จะแสดงวิธีการใช้ XAML + ผสมผสาน SDK เท่านั้น

ฉันเลือกดาวน์โหลดและใช้สองไฟล์จาก Blend SDK ซึ่งทั้งสองอย่างนี้เป็นแพคเกจจาก Microsoft ผ่านทาง NuGet ไฟล์คือ:

System.Windows.Interactivity.dll และ Microsoft.Expression.Interactions.dll

Microsoft.Expression.Interactions.dll ช่วยให้คุณมีความสามารถที่ดีเช่นความสามารถในการตั้งค่าคุณสมบัติหรือเรียกใช้วิธีการใน viewmodel หรือเป้าหมายอื่น ๆ ของคุณและมีเครื่องมืออื่น ๆ ภายในเช่นกัน

XAML บางส่วน:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

โปรดทราบว่าหากคุณเพิ่งจะใช้งานพฤติกรรม OK / Cancel อย่างง่ายคุณสามารถหลีกเลี่ยงการใช้คุณสมบัติ IsDefault และ IsCancel ตราบใดที่หน้าต่างแสดง w / Window.ShowDialog ()
ฉันเองมีปัญหากับปุ่มที่ตั้งค่าคุณสมบัติ IsDefault เป็นจริง แต่ถูกซ่อนไว้เมื่อโหลดหน้าเว็บ ดูเหมือนว่ามันจะไม่ต้องการเล่นอย่างดีหลังจากที่มันถูกแสดงดังนั้นฉันแค่ตั้งค่าคุณสมบัติ Window.DialogResult ดังที่แสดงไว้ด้านบนแทนและมันใช้ได้สำหรับฉัน


0

นี่คือวิธีแก้ไขข้อบกพร่องอย่างง่าย ๆ (พร้อม source code) มันใช้งานได้สำหรับฉัน

  1. สืบทอด ViewModel ของคุณจาก INotifyPropertyChanged

  2. สร้างคุณสมบัติที่สังเกตได้CloseDialog ใน ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    }

  3. แนบตัวจัดการในมุมมองสำหรับการเปลี่ยนแปลงคุณสมบัตินี้

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
  4. ตอนนี้คุณเกือบจะเสร็จแล้ว ในการจัดการเหตุการณ์ทำให้DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }

0

สร้างDependency PropertyในView/ ของคุณUserControl(หรือWindowคุณต้องการปิด) ชอบด้านล่าง:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

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

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

และผูกเข้ากับคุณสมบัติของ ViewModelของคุณ:

<Window x:Class="WpfStackOverflowTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

อสังหาริมทรัพย์ในVeiwModel:

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

ทริกเกอร์การดำเนินการปิดโดยการเปลี่ยนCloseWindowค่าใน ViewModel :)


-2

ในกรณีที่คุณต้องการปิดหน้าต่างเพียงแค่ใส่ใน viewmodel:

TA-da

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

ViewModel ต้องไม่มีUIElementไม่ว่าด้วยวิธีใดเพราะสามารถสร้างบั๊ก
WiiMaxx

จะเกิดอะไรขึ้นถ้า DataContext ที่สืบทอดนั้นมีหลายหน้าต่าง
Kylo Ren

Ta-da, นี่ไม่ใช่ MVVM ทั้งหมด
Alexandru Dicu

-10
Application.Current.MainWindow.Close() 

พอแล้ว!


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