Xử lý các hộp thoại trong WPF với MVVM


235

Trong mẫu MVVM cho WPF, xử lý các hộp thoại là một trong những hoạt động phức tạp hơn. Vì mô hình khung nhìn của bạn không biết gì về khung nhìn, giao tiếp hộp thoại có thể thú vị. Tôi có thể phơi bày mộtICommand cái mà khi khung nhìn gọi nó, một hộp thoại có thể xuất hiện.

Có ai biết một cách tốt để xử lý kết quả từ các hộp thoại? Tôi đang nói về các hộp thoại windows nhưMessageBox .

Một trong những cách chúng tôi đã làm là có một sự kiện trên chế độ xem mà chế độ xem sẽ đăng ký khi cần một hộp thoại.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Điều này không sao, nhưng điều đó có nghĩa là chế độ xem yêu cầu mã là thứ tôi muốn tránh xa.


Tại sao không liên kết với một đối tượng trợ giúp trong Chế độ xem?
Paul Williams

1
Không chắc chắn những gì bạn có ý nghĩa.
Ray Booysen

1
Nếu tôi hiểu câu hỏi, bạn không muốn VM bật lên các hộp thoại và bạn không muốn mã phía sau trong Chế độ xem. Hơn nữa, có vẻ như bạn thích các lệnh cho các sự kiện. Tôi đồng ý với tất cả những điều này, vì vậy tôi sử dụng lớp trình trợ giúp trong Chế độ xem hiển thị lệnh để xử lý hộp thoại. Tôi đã trả lời câu hỏi này trên một chủ đề khác ở đây: stackoverflow.com/a/23303267/420400 . Tuy nhiên, câu cuối cùng có vẻ như bạn không muốn bất kỳnào , ở bất cứ đâu trong Chế độ xem. Tôi hiểu mối quan tâm đó nhưng mã được đề cập chỉ là một điều kiện và nó không có khả năng thay đổi.
Paul Williams

4
Mô hình khung nhìn Thje phải luôn chịu trách nhiệm cho logic đằng sau việc tạo hộp thoại, đó là toàn bộ lý do cho sự tồn tại của nó ở nơi đầu tiên. Điều đó nói rằng nó không (và không nên) thực hiện việc nâng tầm tạo ra khung nhìn. Tôi đã viết một bài viết về chủ đề này tại codeproject.com/Articles/820324/ , trong đó tôi cho thấy rằng toàn bộ vòng đời của các hộp thoại có thể được quản lý thông qua liên kết dữ liệu WPF thông thường và không phá vỡ mẫu MVVM.
Mark Feldman

Câu trả lời:


131

Tôi đề nghị gửi các hộp thoại phương thức của năm 1990 và thay vào đó thực hiện điều khiển dưới dạng lớp phủ (canvas + định vị tuyệt đối) với khả năng hiển thị được gắn với một boolean trở lại trong VM. Gần hơn với một điều khiển loại ajax.

Điều này rất hữu ích:

<BooleanToVisibilityConverter x:Key="booltoVis" />

như trong:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Đây là cách tôi thực hiện như một điều khiển người dùng. Nhấp vào 'x' sẽ đóng điều khiển trong một dòng mã trong mã của điều khiển người dùng phía sau. (Vì tôi có Lượt xem của mình trong .exe và ViewModels trong một dll, tôi không cảm thấy tệ về mã thao túng UI.)

Hộp thoại Wpf


20
Vâng tôi cũng thích ý tưởng này nhưng muốn xem một số ví dụ về điều khiển này về cách hiển thị và lấy kết quả hộp thoại từ nó, v.v. Đặc biệt là trong kịch bản MVVM trong Silverlight.
Roboblob

16
Làm thế nào để bạn ngăn người dùng tương tác với các điều khiển bên dưới lớp phủ hộp thoại này?
Andrew Garrison

16
Vấn đề với cách tiếp cận này là bạn không thể mở hộp thoại phương thức thứ hai từ hộp thoại thứ nhất, ít nhất là không có một số sửa đổi nặng nề đối với hệ thống lớp phủ ...
Thomas Levesque

6
Một vấn đề khác với cách tiếp cận này là "hộp thoại" không thể di chuyển. Trong các ứng dụng của chúng tôi, chúng tôi phải có một hộp thoại có thể di chuyển để người dùng có thể thấy những gì đằng sau nó.
JAB

12
Cách tiếp cận này có vẻ khủng khiếp đối với tôi. Tôi đang thiếu gì? Làm thế nào là tốt hơn so với một hộp thoại thực sự?
Jonathan Wood

51

Bạn nên sử dụng một trung gian hòa giải cho việc này. Người hòa giải là một mẫu thiết kế phổ biến còn được gọi là Messenger trong một số triển khai của nó. Đó là một mô hình của loại Đăng ký / Thông báo và cho phép ViewModel và Chế độ xem của bạn giao tiếp thông qua một cơ chế nhắn tin kết nối thấp.

Bạn nên kiểm tra nhóm Disciples google WPF và chỉ cần tìm kiếm Người hòa giải. Bạn sẽ rất hạnh phúc với câu trả lời ...

Tuy nhiên, bạn có thể bắt đầu với điều này:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-medoder-prototype-for-wpf-apps/

Thưởng thức !

Chỉnh sửa: bạn có thể xem câu trả lời cho vấn đề này với Bộ công cụ ánh sáng MVVM tại đây:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338


2
Marlon grech vừa đăng một triển khai hoàn toàn mới của hòa giải viên: marlongrech.wordpress.com/2009/04/16/ mẹo
Roubachof

21
Chỉ là một nhận xét: mẫu Mediator không được giới thiệu bởi các Disciples WPF, đó là mẫu GoF cổ điển ... ( dofactory.com/Potypes/PotypeMedoder.aspx ). Câu trả lời hay khác;)
Thomas Levesque

10
Chúa ơi, đừng sử dụng một người trung gian hay một sứ giả mờ nhạt. Loại mã với hàng tá thông báo bay xung quanh trở nên rất khó gỡ lỗi trừ khi bạn bằng cách nào đó có thể nhớ tất cả nhiều điểm trong toàn bộ cơ sở mã của mình đăng ký và xử lý mọi sự kiện. Nó trở thành một cơn ác mộng cho các nhà phát triển mới. Trên thực tế, tôi coi toàn bộ thư viện MvvMLight là một mô hình chống lớn cho việc sử dụng tin nhắn không đồng bộ và không cần thiết của nó. Giải pháp rất đơn giản: gọi một dịch vụ hộp thoại riêng (nghĩa là IDialogService) trong thiết kế của bạn. Giao diện có các phương thức và sự kiện cho các cuộc gọi lại.
Chris Bordeman

34

Một hộp thoại MVVM tốt nên:

  1. Được khai báo chỉ với XAML.
  2. Nhận tất cả các hành vi của nó từ cơ sở dữ liệu.

Thật không may, WPF không cung cấp các tính năng này. Hiển thị một hộp thoại yêu cầu một cuộc gọi mã phía sau ShowDialog(). Lớp Window, hỗ trợ các hộp thoại, không thể được khai báo trong XAML để nó không thể dễ dàng được cơ sở dữ liệu choDataContext .

Để giải quyết vấn đề này, tôi đã viết một điều khiển sơ khai XAML nằm trong cây logic và chuyển tiếp cơ sở dữ liệu đến a Windowvà xử lý hiển thị và ẩn hộp thoại. Bạn có thể tìm thấy nó ở đây: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Nó thực sự đơn giản để sử dụng và không yêu cầu bất kỳ thay đổi lạ nào đối với ViewModel của bạn và không yêu cầu các sự kiện hoặc tin nhắn. Cuộc gọi cơ bản trông như thế này:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Bạn có thể muốn thêm một phong cách mà thiết lập Showing. Tôi giải thích nó trong bài viết của tôi. Tôi hy vọng cái này sẽ giúp bạn.


2
Đó là một cách tiếp cận thực sự thú vị cho vấn đề hiển thị các cửa sổ hộp thoại trong MVVM.
dthrasher

2
"Showing a dialog requires a code-behind"mmm bạn có thể gọi nó trong ViewModel
Brock Hensley

Tôi sẽ thêm điểm 3 - bạn có thể tự do liên kết với các đối tượng khác trong chế độ xem. Để lại mã của hộp thoại phía sau trống có nghĩa là không có mã C # ở bất kỳ đâu trong chế độ xem và cơ sở dữ liệu không ngụ ý ràng buộc với VM.
Paul Williams

25

Tôi sử dụng phương pháp này cho các hộp thoại với MVVM.

Tất cả tôi phải làm bây giờ là gọi sau đây từ mô hình xem của tôi.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

uiDialogService đến từ thư viện nào?
aggietech

1
không có thư viện chỉ là một giao diện nhỏ và thực hiện: stackoverflow.com/questions/3801681/ . để công bằng atm nó có một số quá tải cho nhu cầu của tôi :) (chiều cao, chiều rộng, cài đặt tài sản và vv)
blindmeis

16

Giải pháp hiện tại của tôi giải quyết hầu hết các vấn đề bạn đã đề cập nhưng nó hoàn toàn trừu tượng hóa từ những thứ cụ thể của nền tảng và có thể được sử dụng lại. Ngoài ra tôi đã sử dụng không có mã phía sau chỉ ràng buộc với DelegateCommands thực hiện ICommand. Hộp thoại về cơ bản là View - một điều khiển riêng có ViewModel riêng và nó được hiển thị từ ViewModel của màn hình chính nhưng được kích hoạt từ UI thông qua liên kết DelagateCommand.

Xem toàn bộ giải pháp Silverlight 4 tại đây Hộp thoại Modal với MVVM và Silverlight 4


Giống như cách tiếp cận của @Elad Katz, câu trả lời của bạn thiếu nội dung được liên kết - vui lòng cải thiện câu trả lời của bạn bằng cách chèn nó vì đó là câu trả lời tốt ở đây trên SO. Tuy nhiên, cảm ơn sự đóng góp của bạn! :)
Yoda

6

Tôi thực sự vật lộn với khái niệm này trong một thời gian khi học (vẫn học) MVVM. Những gì tôi đã quyết định và những gì tôi nghĩ người khác đã quyết định nhưng điều đó không rõ ràng với tôi là:

Suy nghĩ ban đầu của tôi là ViewModel không được phép gọi trực tiếp hộp thoại vì nó không có tác dụng quyết định cách một hộp thoại sẽ xuất hiện. Vì lý do này, tôi bắt đầu suy nghĩ về cách tôi có thể truyền các thông điệp giống như tôi có trong MVP (tức là View.ShowSaveFileDialog ()). Tuy nhiên, tôi nghĩ rằng đây là cách tiếp cận sai.

ViewModel có thể gọi một hộp thoại trực tiếp. Tuy nhiên, khi bạn đang kiểm tra ViewModel, điều đó có nghĩa là hộp thoại sẽ bật lên trong quá trình thử nghiệm của bạn hoặc thất bại tất cả cùng nhau (chưa bao giờ thực sự thử điều này).

Vì vậy, những gì cần phải xảy ra là trong khi thử nghiệm là sử dụng phiên bản "thử nghiệm" của hộp thoại của bạn. Điều này có nghĩa là đối với bất kỳ hộp thoại nào bạn có, bạn cần tạo Giao diện và giả lập phản hồi hộp thoại hoặc tạo một bản thử nghiệm sẽ có hành vi mặc định.

Bạn nên sử dụng một số loại Trình định vị dịch vụ hoặc IoC mà bạn có thể định cấu hình để cung cấp cho bạn phiên bản chính xác tùy thuộc vào ngữ cảnh.

Sử dụng phương pháp này, ViewModel của bạn vẫn có thể kiểm tra được và tùy thuộc vào cách bạn giả lập các hộp thoại của mình, bạn có thể kiểm soát hành vi.

Hi vọng điêu nay co ich.


6

Có hai cách tốt để làm điều này, 1) dịch vụ hộp thoại (dễ dàng, sạch sẽ) và 2) được hỗ trợ. Xem hỗ trợ cung cấp một số tính năng gọn gàng, nhưng thường không đáng.

DỊCH VỤ DIALOG

a) giao diện dịch vụ hộp thoại như thông qua hàm tạo hoặc một số thùng chứa phụ thuộc:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Việc triển khai IDialogService của bạn sẽ mở một cửa sổ (hoặc đưa một số điều khiển vào cửa sổ đang hoạt động), tạo chế độ xem tương ứng với tên của loại dlgVm đã cho (sử dụng đăng ký hoặc quy ước vùng chứa hoặc ContentPresenter với loại DataTemsheet được liên kết). ShowDialogAsync sẽ tạo một TaskCompletionSource và trả về phần mềm .Task của nó. Bản thân lớp DialogViewModel cần một sự kiện mà bạn có thể gọi trong lớp dẫn xuất khi bạn muốn đóng và xem trong chế độ xem hộp thoại để thực sự đóng / ẩn hộp thoại và hoàn thành TaskCompletionSource.

b) Để sử dụng, chỉ cần gọi await this.DialogService.ShowDialog (myDlgVm) trong trường hợp của bạn về một số lớp có nguồn gốc DialogViewModel. Sau khi chờ trả về, hãy xem các thuộc tính bạn đã thêm trên VM hộp thoại của mình để xác định điều gì đã xảy ra; bạn thậm chí không cần gọi lại.

XEM HIST TRỢ

Điều này có quan điểm của bạn nghe một sự kiện trên viewmodel. Tất cả điều này có thể được gói gọn trong một Hành vi hòa trộn để tránh mã phía sau và việc sử dụng tài nguyên nếu bạn quá thiên về (FMI, phân lớp lớp "Hành vi" để xem một loại thuộc tính có thể trộn được trên steroid). Hiện tại, chúng tôi sẽ thực hiện việc này theo cách thủ công trên mỗi chế độ xem:

a) Tạo OpenXXXXXDialogEvent với tải trọng tùy chỉnh (lớp dẫn xuất DialogViewModel).

b) Có chế độ xem đăng ký sự kiện trong sự kiện OnDataContextChanged của nó. Đảm bảo ẩn và hủy đăng ký nếu giá trị cũ! = Null và trong sự kiện Unloaded của Window.

c) Khi sự kiện kích hoạt, hãy mở chế độ xem của bạn, có thể là tài nguyên trên trang của bạn hoặc bạn có thể định vị nó theo quy ước ở nơi khác (như trong cách tiếp cận dịch vụ hộp thoại).

Cách tiếp cận này linh hoạt hơn, nhưng đòi hỏi nhiều công việc hơn để sử dụng. Tôi không sử dụng nó nhiều. Ví dụ, một lợi thế tốt là khả năng đặt chế độ xem bên trong một tab. Tôi đã sử dụng một thuật toán để đặt nó trong giới hạn của điều khiển người dùng hiện tại hoặc nếu không đủ lớn, đi qua cây thị giác cho đến khi tìm thấy một thùng chứa đủ lớn.

Điều này cho phép các hộp thoại gần với nơi chúng thực sự được sử dụng, chỉ làm mờ phần ứng dụng liên quan đến hoạt động hiện tại và cho phép người dùng di chuyển xung quanh trong ứng dụng mà không cần phải đẩy các hộp thoại theo cách thủ công, thậm chí có nhiều giao diện. hộp thoại phương thức mở trên các tab hoặc chế độ xem phụ khác nhau.


Một dịch vụ hộp thoại dễ dàng hơn nhiều, chắc chắn, và những gì tôi thường làm. Nó cũng giúp dễ dàng đóng hộp thoại của khung nhìn từ mô hình khung nhìn cha, điều này cần thiết khi mô hình khung nhìn cha mẹ đang đóng hoặc hủy.
Chris Bordeman 29/07/2015

4

Sử dụng lệnh freezable

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}

Mã này cần một số công việc, nhưng đó là ý tưởng tốt nhất cho đến nay, đặc biệt là đối với các hộp thoại hệ thống như hộp thoại tệp hoặc máy in. Hộp thoại thuộc về View nếu có bất cứ điều gì. Đối với hộp thoại tệp, kết quả (tên tệp được chọn) có thể được truyền cho lệnh bên trong làm tham số.
Anton Tykhyy

3

Tôi nghĩ rằng việc xử lý một hộp thoại phải là trách nhiệm của khung nhìn và khung nhìn cần phải có mã để hỗ trợ điều đó.

Nếu bạn thay đổi ViewModel - Xem tương tác để xử lý các hộp thoại thì ViewModel phụ thuộc vào việc triển khai đó. Cách đơn giản nhất để giải quyết vấn đề này là làm cho View chịu trách nhiệm thực hiện nhiệm vụ. Nếu điều đó có nghĩa là hiển thị một hộp thoại thì tốt, nhưng cũng có thể là một thông báo trạng thái trong thanh trạng thái, v.v.

Quan điểm của tôi là toàn bộ điểm của mẫu MVVM là tách logic nghiệp vụ khỏi GUI, vì vậy bạn không nên trộn logic GUI (để hiển thị hộp thoại) trong lớp nghiệp vụ (ViewModel).


2
VM sẽ không bao giờ xử lý hộp thoại, trong ví dụ của tôi, nó sẽ có một sự kiện yêu cầu hộp thoại kích hoạt và gửi lại thông tin trong một số dạng EventArss. Nếu khung nhìn chịu trách nhiệm, làm thế nào để nó trả lại thông tin cho VM?
Ray Booysen

Nói VM cần xóa cái gì đó. VM gọi một phương thức trên View Delete để trả về một boolean. Sau đó, View có thể xóa mục trực tiếp và trả về true hoặc hiển thị hộp thoại xác nhận và trả về true / false tùy theo câu trả lời của người dùng.
Cameron MacFarland

VM không biết gì về hộp thoại mà chỉ yêu cầu khung nhìn xóa cái gì đó, khung nhìn được xác nhận hoặc bị từ chối.
Cameron MacFarland

Tôi luôn nghĩ rằng điểm của MVVM là Model: business logic, ViewModel: GUI logic và View: no logic. Đó là một số mâu thuẫn bởi đoạn cuối cùng của bạn. Vui lòng giải thích!
David Schmitt

2
Đầu tiên, nó phải được xác định nếu yêu cầu xác nhận trước khi xóa là logic nghiệp vụ hoặc logic xem. Nếu đó là logic nghiệp vụ, phương thức DeleteFile trong mô hình không được thực hiện mà phải trả về đối tượng câu hỏi xác nhận. Điều này sẽ bao gồm một tham chiếu đến đại biểu thực hiện xóa thực tế. Nếu đó không phải là logic nghiệp vụ, VM phải xây dựng một VM câu hỏi trong DeleteFileCommand, với hai thành viên ICommand. Một cho có và một cho không. Có thể có các đối số cho cả hai chế độ xem và trong RL, hầu hết việc sử dụng có thể sẽ gặp cả hai.
Guge

3

Một thay thế thú vị là sử dụng Bộ điều khiển chịu trách nhiệm hiển thị các khung nhìn (hộp thoại).

Cách thức hoạt động này được hiển thị bởi Khung ứng dụng WPF (WAF) .


3

Tại sao không chỉ nâng một sự kiện trong VM và đăng ký sự kiện trong chế độ xem? Điều này sẽ giữ logic ứng dụng và khung nhìn tách biệt và vẫn cho phép bạn sử dụng cửa sổ con cho các hộp thoại.


3

Tôi đã triển khai một Hành vi nghe Tin nhắn từ ViewModel. Nó dựa trên giải pháp Laurent Bugnion, nhưng vì nó không sử dụng mã phía sau và có thể tái sử dụng nhiều hơn, tôi nghĩ rằng nó thanh lịch hơn.

Cách làm cho WPF hoạt động như thể MVVM được hỗ trợ ngoài hộp


1
Bạn nên bao gồm mã đầy đủ ở đây vì đó là những gì SO yêu cầu cho câu trả lời tốt. Tuy nhiên, cách tiếp cận được liên kết là khá gọn gàng, vì vậy cảm ơn vì điều đó! :)
Yoda

2
@yoda mã đầy đủ khá dài và đó là lý do tại sao tôi muốn liên kết với nó. Tôi đã chỉnh sửa câu trả lời của mình để phản ánh các thay đổi và chỉ ra một liên kết không bị hỏng
Elad Katz

Cảm ơn sự cải thiện. Tuy nhiên, tốt hơn là cung cấp mã 3 cuộn toàn trang dài ở đây trên SO hơn là một liên kết có thể ngoại tuyến một ngày nào đó. Các bài viết hay cho các chủ đề phức tạp luôn khá dài - và tôi không thấy bất kỳ lợi ích nào khi mở một tab mới, chuyển sang nó và cuộn qua đó để cuộn trên cùng một trang / tab tôi đã ở trước đó. ;)
Yoda

@EladKatz Tôi đã thấy rằng bạn đã chia sẻ một số triển khai WPF của bạn trong liên kết bạn cung cấp. Bạn có giải pháp nào để mở một cửa sổ mới từ ViewModel không? Về cơ bản tôi có hai biểu mẫu và mỗi biểu mẫu có một ViewModel. Một người dùng nhấp vào nút một hình thức khác bật lên và viewmodel1 gửi đối tượng của nó đến viewmodel2. Ở dạng 2, người dùng có thể thay đổi đối tượng và khi họ đóng cửa sổ, đối tượng được cập nhật sẽ được gửi trở lại ViewModel đầu tiên. Bạn có giải pháp nào cho việc này không?
Ehsan

2

Tôi nghĩ rằng khung nhìn có thể có mã để xử lý sự kiện từ mô hình khung nhìn.

Tùy thuộc vào sự kiện / kịch bản, nó cũng có thể có trình kích hoạt sự kiện đăng ký để xem các sự kiện mô hình và một hoặc nhiều hành động để gọi để phản hồi.




1

Karl Shifflett đã tạo ra một ứng dụng mẫu để hiển thị các hộp thoại bằng cách sử dụng phương pháp dịch vụ và phương pháp Prism InteractionRequest.

Tôi thích cách tiếp cận dịch vụ - Nó kém linh hoạt hơn nên người dùng ít có khả năng phá vỡ thứ gì đó :) Nó cũng phù hợp với phần WinForms trong ứng dụng của tôi (MessageBox.Show) Nhưng nếu bạn dự định hiển thị nhiều hộp thoại khác nhau, thì InteractionRequest là một cách tốt hơn để đi.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/


1

Tôi biết đó là một câu hỏi cũ, nhưng khi tôi thực hiện tìm kiếm này, tôi tìm thấy rất nhiều câu hỏi liên quan, nhưng tôi không tìm thấy câu trả lời thực sự rõ ràng. Vì vậy, tôi tự thực hiện một hộp thoại / hộp thư / popin và tôi chia sẻ nó!
Tôi nghĩ đó là "bằng chứng MVVM" và tôi cố gắng làm cho nó đơn giản và phù hợp, nhưng tôi chưa quen với WPF, vì vậy hãy bình luận, hoặc thậm chí đưa ra yêu cầu kéo.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Bạn có thể sử dụng nó như thế này:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

Hoặc như thế này nếu bạn muốn popin tinh vi hơn:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

Và nó đang hiển thị những thứ như thế này:

2


1

Cách tiếp cận tiêu chuẩn

Sau nhiều năm đối phó với vấn đề này trong WPF, cuối cùng tôi cũng tìm ra cách tiêu chuẩn để thực hiện các hộp thoại trong WPF. Dưới đây là những ưu điểm của phương pháp này:

  1. DỌN DẸP
  2. Không vi phạm mẫu thiết kế MVVM
  3. ViewModal không bao giờ tham chiếu bất kỳ thư viện UI nào (WindowBase, PresentationFramework, v.v.)
  4. Hoàn hảo để kiểm tra tự động
  5. Hộp thoại có thể được thay thế dễ dàng.

Vậy chìa khóa là gì. Đó là DI + IoC .

Đây là cách nó làm việc. Tôi đang sử dụng MVVM Light, nhưng cách tiếp cận này cũng có thể được mở rộng sang các khung công tác khác:

  1. Thêm một dự án ứng dụng WPF vào giải pháp của bạn. Gọi nó là Ứng dụng .
  2. Thêm một thư viện lớp ViewModal. Gọi nó là VM .
  3. Ứng dụng tham khảo dự án VM. Dự án VM không biết gì về Ứng dụng.
  4. Thêm tham chiếu NuGet vào MVVM Light cho cả hai dự án . Tôi đang sử dụng MVVM Light Standard những ngày này, nhưng bạn cũng không sao với phiên bản Framework đầy đủ.
  5. Thêm giao diện IDialogService vào dự án VM:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Đặt một thuộc tính tĩnh công khai thuộc IDialogServiceloại của bạn ViewModelLocator, nhưng để lại phần đăng ký để lớp View thực hiện. Đây là chìa khóa .:

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Thêm một triển khai giao diện này trong dự án Ứng dụng.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Trong khi một số chức năng này là chung ( ShowMessage, AskBooleanQuestionv.v.), một số chức năng khác dành riêng cho dự án này và sử dụng tùy chỉnh Windows. Bạn có thể thêm nhiều cửa sổ tùy chỉnh theo cùng một cách. Điều quan trọng là giữ các thành phần dành riêng cho UI trong lớp View và chỉ hiển thị dữ liệu được trả về bằng POCO trong lớp VM .
  9. Thực hiện IoC Đăng ký giao diện của bạn trong lớp View bằng lớp này. Bạn có thể làm điều này trong hàm tạo của khung nhìn chính (sau InitializeComponent()cuộc gọi):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Có bạn đi. Bây giờ bạn có quyền truy cập vào tất cả các chức năng hộp thoại của mình ở cả hai lớp VM và View. Lớp VM của bạn có thể gọi các hàm này như thế này:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Vì vậy, bạn thấy sạch sẽ. Lớp VM không biết gì về cách câu hỏi Có / Không sẽ được trình bày cho người dùng bởi lớp UI và vẫn có thể hoạt động thành công với kết quả trả về từ hộp thoại.

Các đặc quyền miễn phí khác

  1. Để viết thử nghiệm đơn vị, bạn có thể cung cấp một triển khai tùy chỉnh IDialogServicetrong dự án Thử nghiệm của mình và đăng ký lớp đó trong IoC trong hàm tạo của lớp thử nghiệm của bạn.
  2. Bạn sẽ cần nhập một số không gian tên như Microsoft.Win32để truy cập hộp thoại Mở và Lưu. Tôi đã loại bỏ chúng vì cũng có phiên bản WinForms của các hộp thoại này, cộng với ai đó có thể muốn tạo phiên bản của riêng họ. Cũng lưu ý rằng một số định danh được sử dụng DialogPresenterlà tên của các cửa sổ của riêng tôi (ví dụ SettingsWindow). Bạn sẽ cần xóa chúng khỏi cả giao diện và cách triển khai hoặc cung cấp các cửa sổ của riêng bạn.
  3. Nếu VM của bạn thực hiện đa luồng, hãy gọi MVVM Light DispatcherHelper.Initialize()sớm trong vòng đời của ứng dụng.
  4. Ngoại trừ DialogPresenterđược chèn vào lớp View, các ViewModals khác phải được đăng ký ViewModelLocatorvà sau đó một thuộc tính tĩnh công khai của loại đó sẽ được hiển thị cho lớp View để tiêu thụ. Một cái gì đó như thế này:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. Đối với hầu hết các phần, các hộp thoại của bạn không nên có bất kỳ mã phía sau nào cho các thứ như ràng buộc hoặc cài đặt DataContext, v.v. Bạn thậm chí không nên chuyển mọi thứ dưới dạng tham số của hàm tạo. XAML có thể làm tất cả cho bạn, như thế này:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Đặt DataContextcách này cung cấp cho bạn tất cả các loại lợi ích trong thời gian thiết kế như Intellisense và tự động hoàn thành.

Mong rằng sẽ giúp tất cả mọi người.


0

Tôi đã suy nghĩ một vấn đề tương tự khi hỏi mô hình khung nhìn cho một tác vụ hoặc hộp thoại sẽ như thế nào .

Giải pháp hiện tại của tôi trông như thế này:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Khi mô hình khung nhìn quyết định rằng đầu vào của người dùng là bắt buộc, nó sẽ đưa ra một thể hiện SelectionTaskModelvới các lựa chọn có thể có cho người dùng. Cơ sở hạ tầng đảm nhiệm việc đưa ra chế độ xem tương ứng, trong thời gian thích hợp sẽ gọi Choose()chức năng với sự lựa chọn của người dùng.


0

Tôi vật lộn với cùng một vấn đề. Tôi đã nghĩ ra một cách để giao tiếp giữa Chế độ xem và Chế độ xem. Bạn có thể bắt đầu gửi tin nhắn từ ViewModel đến View để yêu cầu nó hiển thị hộp thông báo và nó sẽ báo cáo lại với kết quả. Sau đó, ViewModel có thể phản hồi kết quả được trả về từ Chế độ xem.

Tôi chứng minh điều này trong blog của mình :


0

Tôi đã viết một bài viết khá toàn diện về chính chủ đề này và cũng đã phát triển một thư viện pop-in cho Hộp thoại MVVM. Việc tuân thủ nghiêm ngặt MVVM không chỉ có thể mà còn rất sạch sẽ khi được triển khai đúng cách và có thể dễ dàng mở rộng sang các thư viện bên thứ ba không tuân thủ chính nó:

https://www.codeproject.com/Articles/820324/Im Hiệning-Deog-Boxes-in-MDVM


0

Xin lỗi, nhưng tôi phải kêu gọi. Tôi đã xem qua một số giải pháp được đề xuất, trước khi tìm thấy không gian tên Prism.Wpf.Interactivity trong dự án Prism. Bạn có thể sử dụng các yêu cầu tương tác và hành động cửa sổ bật lên để cuộn một cửa sổ tùy chỉnh hoặc cho các nhu cầu đơn giản hơn được tích hợp trong cửa sổ bật lên Thông báo và Xác nhận. Chúng tạo ra các cửa sổ thực sự và được quản lý như vậy. bạn có thể vượt qua một đối tượng bối cảnh với bất kỳ phụ thuộc nào bạn cần trong hộp thoại. Chúng tôi sử dụng giải pháp này tại nơi làm việc của tôi kể từ khi tôi tìm thấy nó. Chúng tôi có nhiều nhà phát triển cao cấp ở đây và không ai nghĩ ra điều gì tốt hơn. Giải pháp trước đây của chúng tôi là dịch vụ hộp thoại thành một lớp phủ và sử dụng lớp người trình bày để thực hiện điều đó, nhưng bạn phải có các nhà máy cho tất cả các chế độ xem hộp thoại, v.v.

Điều này không tầm thường nhưng cũng không quá phức tạp. Và nó được xây dựng trong Prism và do đó tốt nhất (hoặc tốt hơn) thực hành IMHO.

2 xu của tôi!


-1

EDIT: có, tôi đồng ý rằng đây không phải là một cách tiếp cận MVVM chính xác và hiện tôi đang sử dụng một cái gì đó tương tự như những gì được đề xuất bởi blindmeis.

Một trong những cách bạn có thể làm điều này là

Trong Mô hình Chế độ xem Chính của bạn (nơi bạn mở phương thức):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

Và trong Chế độ xem cửa sổ / ViewModel của bạn:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

hoặc tương tự với những gì được đăng ở đây WPVM MVVM: Cách đóng cửa sổ


2
Tôi không phải là downvote, nhưng tôi nghi ngờ đó là vì mô hình khung nhìn có tham chiếu trực tiếp đến khung nhìn.
Brian Gideon

@BrianGideon, cảm ơn bình luận của bạn. Tôi đồng ý đây không phải là một giải pháp tách rời. Trong thực tế, tôi không sử dụng một cái gì đó tương tự như whar được đề xuất bởi blindmeis. Cảm ơn một lần nữa.
Simone

Đó là hình thức xấu để tiếp cận với tầm nhìn khi không dễ dàng như vậy.
Chris Bordeman 29/07/2015
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.