MVVM을 사용하여 WPF에서 대화 상자 처리


235

WPF의 MVVM 패턴에서 대화 상자 처리는보다 복잡한 작업 중 하나입니다. 뷰 모델은 뷰에 대해 아무것도 모르므로 대화 대화가 흥미로울 수 있습니다. ICommand뷰가 호출 할 때 대화 상자가 나타날 수 있다는 것을 노출시킬 수 있습니다.

누구든지 대화 상자의 결과를 처리하는 좋은 방법을 알고 있습니까? 와 같은 Windows 대화 상자에 대해 말하고 MessageBox있습니다.

이 작업을 수행 한 방법 중 하나는 대화 상자가 필요할 때 뷰가 구독하는 뷰 모델의 이벤트가있었습니다.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

이것은 괜찮지 만 뷰에 멀리 떨어져있는 코드가 필요하다는 것을 의미합니다.


뷰에서 헬퍼 객체에 바인딩하지 않는 이유는 무엇입니까?
Paul Williams

1
당신이 무슨 뜻인지 확실하지.
Ray Booysen

1
질문을 이해하면 VM 팝업 대화 상자를 원하지 않고 View에서 코드 숨김을 원하지 않습니다. 또한 이벤트보다 명령을 선호하는 것처럼 들립니다. 나는이 모든 것에 동의하므로 대화 상자를 처리하는 명령을 표시하는 View에서 도우미 클래스를 사용합니다. 나는 또 다른 스레드 에서이 질문에 대답했습니다 : stackoverflow.com/a/23303267/420400 . 그러나 마지막 문장을 원하지 않는 것처럼 들리게 어떤 모두에서 코드를 어디서나 보기에. 우려 사항을 이해하지만 문제의 코드는 조건부 일 뿐이며 변경되지 않을 것입니다.
Paul Williams

4
이 뷰 모델은 항상 대화 상자 생성의 배후에있는 논리를 책임 져야합니다. 이것이 처음부터 존재하는 모든 이유입니다. 그것은 뷰 자체를 만드는 데 드는 일을하지 말아야한다고 말해서는 안됩니다. codeproject.com/Articles/820324/ 에서이 주제에 대한 기사를 썼습니다. 여기서 대화 상자의 전체 수명주기는 일반적인 WPF 데이터 바인딩을 통해 MVVM 패턴을 손상시키지 않고 관리 할 수 ​​있음을 보여줍니다.
Mark Feldman

답변:


131

1990 년대 모달 대화 상자를 포기하고 대신 VM의 부울에 연결된 가시성을 가진 오버레이 (캔버스 + 절대 위치 지정)로 컨트롤을 구현하는 것이 좋습니다. 아약스 타입 컨트롤에 더 가깝습니다.

이것은 매우 유용합니다 :

<BooleanToVisibilityConverter x:Key="booltoVis" />

에서와 같이 :

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

다음은 사용자 정의 컨트롤로 구현 한 방법입니다. 'x'를 클릭하면 usercontrol의 코드 뒤에 코드 줄로 컨트롤이 닫힙니다. .exe에 뷰가 있고 dll에 ViewModels가 있으므로 UI를 조작하는 코드에 대해 나쁘지 않습니다.

Wpf 대화 상자


20
그래, 나는이 아이디어도 좋아하지만 그것을 보여주는 방법과 관련 하여이 컨트롤의 예를보고 그것을 통해 대화 결과를 검색하고 싶습니다. 특히 Silverlight의 MVVM 시나리오에서.
Roboblob

16
사용자가이 대화 상자 오버레이 아래의 컨트롤과 상호 작용하지 못하게하려면 어떻게합니까?
앤드류 개리슨

16
이 접근법의 문제점은 최소한 오버레이 시스템을 약간 수정하지 않으면 서 첫 번째 대화 상자에서 두 번째 모달 대화 상자를 열 수 없다는 것입니다.
Thomas Levesque

6
이 방법의 또 다른 문제점은 "대화 상자"를 이동할 수 없다는 것입니다. 우리의 응용 프로그램에는 사용자가 대화 상자 뒤에 무엇이 있는지 볼 수 있도록 움직일 수있는 대화 상자가 있어야합니다.
JAB

12
이 접근법은 나에게 끔찍한 것 같습니다. 내가 무엇을 놓치고 있습니까? 이것이 실제 대화 상자보다 나은 점은 무엇입니까?
Jonathan Wood

51

이를 위해 중개자를 사용해야합니다. 중재자는 일부 구현에서 메신저 라고도하는 일반적인 디자인 패턴 입니다. 이는 Register / Notify 유형의 패러다임이며 ViewModel 및 Views가 낮은 결합 메시징 메카니즘을 통해 통신 할 수 있도록합니다.

Google WPF 제자 그룹을 확인하고 중재자를 검색해야합니다. 당신은 대답에 매우 만족할 것입니다 ...

그러나 다음과 같이 시작할 수 있습니다.

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

즐겨 !

편집 : MVVM Light Toolkit 으로이 문제에 대한 답을 볼 수 있습니다.

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


2
말론 그레 흐는 방금 중재자의 새로운 구현을 게시했습니다 : marlongrech.wordpress.com/2009/04/16/…
Roubachof

21
단지 말 : WPF 제자들에 의해 매개체 패턴이 도입 된 것은 아니며, 고전적인 GoF 패턴입니다 ( dofactory.com/Patterns/PatternMediator.aspx ). 그렇지 않으면 좋은 답)
토마스 레베

10
제발, 중재자 나 대담한 메신저를 사용하지 마십시오. 모든 이벤트를 구독하고 처리하는 전체 코드베이스의 모든 지점을 어떻게 든 기억할 수 없다면 수십 개의 메시지가있는 그러한 종류의 코드는 디버깅하기가 매우 어려워집니다. 새로운 개발자에게는 악몽이됩니다. 사실, 나는 전체 MvvMLight 라이브러리가 광범위하고 불필요하게 비동기 메시징을 사용하기위한 방대한 안티 패턴이라고 생각합니다. 해결책은 간단합니다. 디자인의 별도의 대화 서비스 (즉, IDialogService)를 호출하십시오. 인터페이스에는 콜백을위한 메소드와 이벤트가 있습니다.
Chris Bordeman

34

좋은 MVVM 대화 상자는 다음과 같아야합니다.

  1. XAML로만 선언하십시오.
  2. 데이터 바인딩에서 모든 동작을 가져옵니다.

불행히도 WPF는 이러한 기능을 제공하지 않습니다. 대화 상자를 표시하려면에 대한 코드 숨김 호출이 필요합니다 ShowDialog(). 대화 상자를 지원하는 Window 클래스는 XAML에서 선언 할 수 없으므로에 데이터 바인딩하기가 쉽지 않습니다 DataContext.

이 문제를 해결하기 위해 논리 트리에 앉아 데이터 바인딩을 a로 릴레이 Window하고 대화 상자 표시 및 숨기기를 처리 하는 XAML 스텁 컨트롤을 작성했습니다 . 여기에서 찾을 수 있습니다 : http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

실제로 사용하기 쉽고 ViewModel을 이상하게 변경하지 않아도되며 이벤트 나 메시지가 필요하지 않습니다. 기본 호출은 다음과 같습니다.

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

설정하는 스타일을 추가하고 싶을 것입니다 Showing. 내 기사에서 설명합니다. 이것이 도움이되기를 바랍니다.


2
MVVM에서 대화 상자 창을 표시하는 문제에 대한 정말 흥미로운 접근 방식입니다.
dthrasher

2
"Showing a dialog requires a code-behind"당신은 ViewModel에서 그것을 호출 할 수 있습니다
Brock Hensley

포인트 3을 추가합니다.보기 내 다른 객체에 자유롭게 바인딩 할 수 있습니다. 대화 상자의 코드를 비워두면 뷰에 C # 코드가 없으며 데이터 바인딩이 VM에 바인딩되는 것을 의미하지 않습니다.
Paul Williams

25

MVVM과의 대화 에이 방법을 사용 합니다 .

지금해야 할 일은 뷰 모델에서 다음을 호출하는 것입니다.

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

uiDialogService는 어떤 라이브러리에서 제공됩니까?
aggietech

1
도서관이 없습니다. 단지 작은 인터페이스 및 구현입니다 : stackoverflow.com/questions/3801681/… . 공정은 기압이 내 요구 :)에 대한 (높이, 폭, propertysettings 등) 좀 더 과부하가 될 수 있습니다
blindmeis

16

내 현재 솔루션은 언급 한 대부분의 문제를 해결하지만 플랫폼 관련 사항에서 완전히 추상화되어 재사용 할 수 있습니다. 또한 ICommand를 구현하는 DelegateCommands와의 바인딩 만 코드 숨김을 사용하지 않았습니다. Dialog는 기본적으로 View-자체 ViewModel을 가진 별도의 컨트롤이며 기본 화면의 ViewModel에서 표시되지만 DelagateCommand 바인딩을 통해 UI에서 트리거됩니다.

MVVM 및 Silverlight 4를 사용한 모달 대화 상자는 여기에서 전체 Silverlight 4 솔루션을 참조하십시오.


@Elad Katz의 접근 방식과 마찬가지로 답변에 링크 된 내용이 없습니다. 여기에 좋은 답변으로 간주되는 답변을 삽입하여 답변을 개선하십시오. 그럼에도 귀하의 기여에 감사드립니다! :)
Yoda

6

나는 MVVM을 배울 때 (정지 학습) 잠시 동안이 개념으로 어려움을 겪었습니다. 내가 결정한 것과 다른 사람들이 이미 결정했지만 분명하지 않은 것은 다음과 같습니다.

내 원래 생각은 ViewModel이 대화 상자를 표시하는 방법을 결정하는 비즈니스가 없기 때문에 대화 상자를 직접 호출 할 수 없다는 것입니다. 이 때문에 나는 MVP에서와 같이 메시지를 전달할 수있는 방법에 대해 생각하기 시작했다 (예 : View.ShowSaveFileDialog ()). 그러나 이것이 잘못된 접근법이라고 생각합니다.

ViewModel이 대화 상자를 직접 호출해도됩니다. 그러나 ViewModel을 테스트 할 때 테스트 중에 대화 상자가 팝업되거나 모두 실패합니다 (실제로 시도하지는 않음).

따라서 테스트하는 동안 대화 상자의 "테스트"버전을 사용해야합니다. 즉, 대화 상자가 있으면 항상 인터페이스를 작성하고 대화 상자 응답을 모의하거나 기본 동작을 갖는 테스트 모의를 작성해야합니다.

컨텍스트에 따라 올바른 버전을 제공하도록 구성 할 수있는 일종의 Service Locator 또는 IoC를 이미 사용 중이어야합니다.

이 접근 방식을 사용하면 ViewModel을 계속 테스트 할 수 있으며 대화 상자를 조롱하는 방법에 따라 동작을 제어 할 수 있습니다.

도움이 되었기를 바랍니다.


6

이를 수행하는 두 가지 좋은 방법이 있습니다. 1) 대화 서비스 (쉽고 깨끗함)와 2)보기 지원. View Assisted는 몇 가지 깔끔한 기능을 제공하지만 일반적으로 가치가 없습니다.

대화 서비스

a) 생성자 또는 일부 종속성 컨테이너와 같은 대화 서비스 인터페이스 :

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) IDialogService를 구현하면 창을 열거 나 (또는 ​​일부 제어를 활성 창에 주입) 주어진 dlgVm 유형의 이름 (컨테이너 등록 또는 규칙 사용 또는 DataTemplate과 관련된 ContentPresenter 사용)을 작성해야합니다. ShowDialogAsync는 TaskCompletionSource를 생성하고 .Task 속성을 반환해야합니다. DialogViewModel 클래스 자체는 닫으려고 할 때 파생 클래스에서 호출 할 수있는 이벤트가 필요하며 대화 상자보기에서 실제로 대화 상자를 닫거나 숨기고 TaskCompletionSource를 완료하는 것을 볼 수 있습니다.

b) 사용하려면 DialogViewModel 파생 클래스의 인스턴스에서 await this.DialogService.ShowDialog (myDlgVm)를 호출하십시오. 대기 상태로 돌아간 후 대화 VM에서 추가 한 속성을보고 무슨 일이 있었는지 확인하십시오. 콜백이 필요하지 않습니다.

지원보기

뷰 모델의 이벤트를 듣는 뷰가 있습니다. 이것은 기울어 진 코드와 리소스 사용을 피하기 위해 블렌드 비헤이비어로 모두 묶을 수 있습니다 (FMI, 스테로이드에 일종의 블렌딩 가능한 속성을보기 위해 "비헤이비어"클래스를 서브 클래 싱). 지금은 각보기에서 수동으로 수행합니다.

a) 커스텀 페이로드 (DialogViewModel 파생 클래스)로 OpenXXXXXDialogEvent를 만듭니다.

b) 뷰가 OnDataContextChanged 이벤트에서 이벤트를 구독하도록합니다. 이전 값! = null이고 Window의 Unloaded 이벤트에서 숨기거나 구독 취소해야합니다.

c) 이벤트가 발생하면 페이지의 리소스에있을 수있는보기가 열리도록하거나 대화 상자 서비스 접근 방식과 같이 다른 곳에서 규칙을 통해 찾을 수 있습니다.

이 방법은 더 유연하지만 더 많은 작업이 필요합니다. 많이 사용하지 않습니다. 한 가지 좋은 장점은 예를 들어보기를 실제로 탭 안에 배치 할 수 있다는 것입니다. 알고리즘을 사용하여 현재 사용자 컨트롤의 경계에 배치하거나 충분히 크지 않은 경우 큰 컨테이너가 발견 될 때까지 시각적 트리를 탐색합니다.

이를 통해 대화 상자는 실제 사용되는 장소에 가깝게 접근 할 수 있으며 현재 활동과 관련된 앱의 일부만 어둡게하고 사용자가 대화 상자를 수동으로 밀지 않고도 앱 내에서 이동할 수 있습니다. 모달 대화 상자가 다른 탭 또는 하위보기에서 열립니다.


대화 서비스는 확실히 훨씬 쉽고 내가 보통하는 일입니다. 또한 상위 뷰 모델을 닫거나 취소 할 때 필요한 상위 뷰 모델에서 뷰 대화 상자를 쉽게 닫을 수 있습니다.
Chris Bordeman

4

고정 가능한 명령 사용

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

이 코드는 약간의 작업이 필요하지만, 특히 파일 또는 프린터 대화 상자와 같은 시스템 대화 상자에 가장 좋습니다. 대화 상자가 있으면 View에 속합니다. 파일 대화 상자의 경우 결과 (파일 이름 선택)를 매개 변수로 내부 명령에 전달할 수 있습니다.
Anton Tykhyy 2016 년

3

대화 상자를 처리하는 것이 뷰의 책임이어야하며 뷰에는이를 지원하는 코드가 있어야한다고 생각합니다.

대화 상자를 처리하기 위해 ViewModel-View 상호 작용을 변경하면 ViewModel은 해당 구현에 따라 다릅니다. 이 문제를 처리하는 가장 간단한 방법은 작업을 수행하는 데 View를 작성하는 것입니다. 이것이 대화 상자를 표시하는 것을 의미하지만 상태 표시 줄에 상태 메시지가 될 수도 있습니다.

필자의 요점은 MVVM 패턴의 요점은 비즈니스 로직을 GUI와 분리하는 것이므로 비즈니스 계층 (ViewModel)에서 GUI 로직을 혼합 (대화 상자를 표시하기 위해)해서는 안됩니다.


2
VM은 대화 상자를 처리하지 않습니다. 제 예에서는 대화 상자를 실행하여 특정 형식의 EventArgs로 정보를 전달 해야하는 이벤트가 있습니다. 뷰가 책임이 있다면 어떻게 정보를 VM으로 다시 전달합니까?
Ray Booysen

VM이 무언가를 삭제해야한다고 가정 해보십시오. VM은 View Delete에서 부울을 반환하는 메서드를 호출합니다. 그런 다음보기는 항목을 직접 삭제하고 true를 반환하거나 사용자의 답변에 따라 확인 대화 상자를 표시하고 true / false를 반환 할 수 있습니다.
Cameron MacFarland

VM은 대화 상자에 대해 아무것도 모르지만보기에 확인 또는 거부 된 항목 만 삭제하도록 요청했습니다.
Cameron MacFarland

나는 항상 MVVM의 핵심은 Model : business logic, ViewModel : GUI logic 및 View : no logic이라고 생각했습니다. 마지막 단락과 모순됩니다. 설명 해주십시오!
David Schmitt

2
먼저 사전 삭제 확인을 요청하는 것이 비즈니스 로직인지 또는 뷰 로직인지 확인해야합니다. 비즈니스 로직 인 경우 모델의 DeleteFile 메소드는이를 수행하지 말고 확인 질문 오브젝트를 리턴해야합니다. 여기에는 실제 삭제를 수행하는 대리자에 대한 참조가 포함됩니다. 비즈니스 로직이 아닌 경우 VM은 DeleteFileCommand에서 두 개의 ICommand 멤버를 사용하여 질문의 VM을 빌드해야합니다. 하나는 예, 하나는 아니오입니다. 두보기 모두에 대한 인수가있을 수 있으며 RL에서는 대부분 두 가지가 모두 사용됩니다.
Guge


3

VM에서 이벤트를 발생시키고 뷰에서 이벤트를 구독하지 않는 이유는 무엇입니까? 이렇게하면 응용 프로그램 논리와 뷰가 분리되어 있으며 대화 상자에 자식 창을 계속 사용할 수 있습니다.


3

ViewModel에서 메시지를 수신하는 동작을 구현했습니다. Laurent Bugnion 솔루션을 기반으로하지만 코드를 사용하지 않고 재사용 할 수 있기 때문에 더 우아하다고 생각합니다.

MPF가 즉시 지원되는 것처럼 WPF를 작동시키는 방법


1
여기에 전체 코드를 포함시켜야 좋은 답변을 얻을 수 있습니다. 그럼에도 불구하고 연결된 접근법은 매우 깔끔하므로 감사합니다! :)
Yoda

2
@yoda 전체 코드는 꽤 길기 때문에 오히려 링크에 연결해야합니다. 변경 사항을 반영하고 끊어지지 않은 링크를 가리 키도록 답변을 편집했습니다.
Elad Katz

개선 주셔서 감사합니다. 그럼에도 불구하고 언젠가 오프라인 상태 일 수있는 링크보다 여기에 긴 코드 3 전체 페이지 스크롤을 제공하는 것이 좋습니다. 복잡한 주제에 대한 좋은 기사는 항상 꽤 길며 새 탭을 열고 탭으로 전환 한 다음 이전 페이지와 동일한 페이지 / 탭에서 스크롤하여 스크롤해도 아무런 이점이 없습니다. ;)
Yoda

@EladKatz 귀하가 제공 한 링크에서 WPF 구현 중 일부를 공유 한 것으로 나타났습니다. ViewModel에서 새 창을 여는 솔루션이 있습니까? 기본적으로 두 가지 형식이 있으며 각각 하나의 ViewModel이 있습니다. 한 사용자가 다른 양식이 팝업되는 버튼을 클릭하면 viewmodel1이 해당 객체를 viewmodel2로 보냅니다. 양식 2에서 사용자는 객체를 변경할 수 있으며 창을 닫으면 업데이트 된 객체가 첫 번째 ViewModel로 다시 전송됩니다. 이에 대한 해결책이 있습니까?
Ehsan

2

뷰에는 뷰 모델의 이벤트를 처리하는 코드가있을 수 있다고 생각합니다.

이벤트 / 시나리오에 따라 모델 이벤트를보기 위해 등록하는 이벤트 트리거 및 응답으로 호출 할 하나 이상의 조치가있을 수도 있습니다.




1

Karl Shifflett은 서비스 접근 방식 및 Prism InteractionRequest 접근 방식을 사용하여 대화 상자를 표시하기위한 샘플 애플리케이션을 작성했습니다.

나는 서비스 접근 방식을 좋아한다-유연성이 떨어지기 때문에 사용자가 무언가를 깨뜨릴 가능성이 적다 :) 또한 내 응용 프로그램 (MessageBox.Show)의 WinForms 부분과 일치하지만 많은 다른 대화 상자를 표시하려는 경우 InteractionRequest는 더 나은 방법.

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


1

나는 그것이 오래된 질문이라는 것을 알고 있지만,이 검색을 할 때 많은 관련 질문을 찾았지만 실제로 명확한 대답을 찾지 못했습니다. 대화 상자 / 메시지 상자 / 팝을 직접 구현하고 공유합니다!
나는 그것이 "MVVM 증거"라고 생각하고 그것을 간단하고 적절하게 만들려고 노력하지만 WPF를 처음 사용하므로 자유롭게 의견을 말하거나 풀 요청을하십시오.

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

다음과 같이 사용할 수 있습니다.

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)
        // [...]
}

또는 더 세련된 popin을 원한다면 다음과 같이하십시오.

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

그리고 그것은 다음과 같은 것을 보여줍니다 :

2


1

표준 접근법

WPF 에서이 문제를 해결하는 데 수년을 보낸 후 마침내 WPF에서 대화 상자를 구현 하는 표준 방법을 찾았습니다 . 이 방법의 장점은 다음과 같습니다.

  1. 깨끗한
  2. MVVM 디자인 패턴을 위반하지 않습니다
  3. ViewModal은 UI 라이브러리 (WindowBase, PresentationFramework 등)를 절대 참조하지 않습니다.
  4. 자동 테스트에 적합
  5. 대화 상자를 쉽게 교체 할 수 있습니다.

열쇠가 뭐야? 그것은이다 DI + IOC의 .

작동 방식은 다음과 같습니다. MVVM Light를 사용하고 있지만이 방법은 다른 프레임 워크로 확장 될 수 있습니다.

  1. 솔루션에 WPF Application 프로젝트를 추가하십시오. 그것을 App 이라고 부릅니다 .
  2. ViewModal 클래스 라이브러리를 추가하십시오. 그것을 VM 이라고 부릅니다 .
  3. 앱은 VM 프로젝트를 참조합니다. VM 프로젝트는 앱에 대해 아무것도 모릅니다.
  4. 두 프로젝트 모두 에 MVVM Light에 대한 NuGet 참조를 추가하십시오 . 요즘 MVVM Light Standard를 사용 하고 있지만 풀 프레임 워크 버전도 괜찮습니다.
  5. IDialogService 인터페이스 를 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. IDialogService유형 의 공개 정적 속성을 노출 ViewModelLocator하지만 뷰 레이어가 수행 할 등록 부분을 남겨 둡니다. 이것이 핵심 입니다. :

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. 앱 프로젝트에서이 인터페이스의 구현을 추가하십시오.

    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. 이러한 기능 중 일부는 (일반적인 반면 ShowMessage, AskBooleanQuestion등), 다른 사람이 프로젝트 사용 사용자 정의에 고유 Window의. 같은 방식으로 더 많은 사용자 정의 창을 추가 할 수 있습니다. 핵심은 View 계층에 UI 특정 요소를 유지하고 VM 계층에서 POCO를 사용하여 반환 된 데이터를 노출시키는 것입니다. 입니다.
  9. 이 클래스를 사용하여 View 계층에서 인터페이스를 IoC 등록하십시오. 메인 뷰의 생성자 ( InitializeComponent()호출 후 ) 에서이 작업을 수행 할 수 있습니다 .

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. 당신은 간다. 이제 VM 및 View 계층 모두에서 모든 대화 상자 기능에 액세스 할 수 있습니다. VM 계층은 다음과 같이 이러한 기능을 호출 할 수 있습니다.

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. 그래서 당신은 깨끗합니다. VM 계층은 UI 계층에서 예 / 아니오 질문을 사용자에게 표시하는 방법에 대해 전혀 모르며 대화 상자에서 반환 된 결과를 계속 사용할 수 있습니다.

다른 무료 특전

  1. 단위 테스트를 작성하려면 IDialogService테스트 프로젝트에서 사용자 정의 구현을 제공하고 해당 클래스를 테스트 클래스 생성자에서 IoC에 등록 할 수 있습니다.
  2. Microsoft.Win32열기 및 저장 대화 상자에 액세스하는 등 일부 네임 스페이스를 가져와야 합니다. 이 대화 상자의 WinForms 버전도 제공되므로 누군가가 자신의 버전을 만들고 싶을 수도 있습니다. 또한 사용 된 식별자 중 일부는 DialogPresenter내 창 이름 (예 :) SettingsWindow입니다. 인터페이스와 구현에서 모두 제거하거나 자신의 창을 제공해야합니다.
  3. VM이 멀티 스레딩을 수행하는 경우 DispatcherHelper.Initialize()애플리케이션 수명주기 초기에 MVVM Light를 호출 하십시오.
  4. DialogPresenterView 레이어에 주입되는 것을 제외하고 다른 ViewModals에 등록한 ViewModelLocator다음 해당 유형의 공개 정적 속성을 노출하여 View 레이어를 사용하십시오. 이 같은:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. 대부분의 경우 대화 상자에는 DataContext 바인딩 또는 설정 등과 같은 항목에 대한 코드 숨김이 없어야합니다. 생성자 매개 변수로 항목을 전달해서는 안됩니다. XAML은 다음과 같이 모든 것을 할 수 있습니다.

    <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. DataContext이 방법을 설정 하면 Intellisense 및 자동 완성과 같은 모든 종류의 디자인 타임 이점이 제공됩니다.

그것이 모두를 돕는 희망입니다.


0

작업 또는 대화 상자의 뷰 모델이 어떻게 보이는지 묻고 비슷한 문제를 고민하고 있었습니다.

내 현재 솔루션은 다음과 같습니다.

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

뷰 모델이 사용자 입력이 필요하다고 결정하면 사용자가 SelectionTaskModel선택할 수 있는 인스턴스를 가져옵니다 . 인프라 스트럭처는 적절한 시점 Choose()에 사용자가 선택한 기능을 호출 할 수 있도록 해당 뷰를 표시 합니다.


0

나는 같은 문제로 어려움을 겪었다. View와 ViewModel 사이의 상호 통신 방법을 생각해 냈습니다. ViewModel에서 View로 메시지 보내기를 시작하여 메시지 상자를 표시하도록 지시하면 결과와 함께 다시보고합니다. 그런 다음 ViewModel은 View에서 반환 된 결과에 응답 할 수 있습니다.

내 블로그 에서 이것을 보여줍니다 .



0

죄송하지만 Prism 프로젝트에서 Prism.Wpf.Interactivity 네임 스페이스를 찾기 전에 몇 가지 제안 된 솔루션을 살펴 보았습니다. 상호 작용 요청 및 팝업 창 조치를 사용하여 사용자 정의 창을 롤링하거나 알림 및 확인 팝업에 내장 된 간단한 요구 사항을 수행 할 수 있습니다. 이들은 진정한 창을 생성하고 그대로 관리됩니다. 대화 상자에서 필요한 종속성을 가진 컨텍스트 객체를 전달할 수 있습니다. 우리는 내가 찾은 이후이 솔루션을 직장에서 사용합니다. 우리는 여기에 많은 선임 개발자가 있으며 아무도 더 나은 것을 생각해 냈습니다. 우리의 이전 솔루션은 오버레이에 대화 서비스를 제공하고 발표자 클래스를 사용하여이를 수행했지만 모든 대화 상자 뷰 모델 등에 대한 팩토리가 있어야했습니다.

이것은 사소한 것이 아니지만 매우 복잡하지도 않습니다. 그리고 그것은 프리즘에 내장되어 있으므로 IMHO를 연습하는 것이 가장 좋습니다.

내 2 센트!


-1

편집 : 예 나는 이것이 올바른 MVVM 접근 방식이 아니라는 것에 동의하며 현재 blindmeis가 제안한 것과 비슷한 것을 사용하고 있습니다.

당신이 할 수있는 방법 중 하나는

메인 뷰 모델에서 (모달을 여는 위치) :

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

그리고 모달 창보기 /보기 모델에서 :

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>

뷰 모델 :

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

또는 여기에 게시 된 것과 유사합니다. WPF MVVM : 창을 닫는 방법


2
나는 downvote가 아니었지만 뷰 모델에 뷰에 대한 직접적인 참조가 있기 때문이라고 생각합니다.
Brian Gideon

@BrianGideon, 귀하의 의견에 감사드립니다. 나는 이것이 분리 된 솔루션이 아니라는 데 동의합니다. 사실, 나는 blindmeis가 제안한 whar와 비슷한 것을 사용하지 않습니다. 다시 감사합니다.
Simone

보기가 쉽지 않을 때는보기에 좋지 않은 형태입니다.
Chris Bordeman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.