MVVM을 사용하는 wpf의 대화 상자에 대한 좋은 습관 또는 나쁜 습관?


148

최근에 wpf 앱에 대한 추가 및 편집 대화 상자를 만드는 데 문제가있었습니다.

내 코드에서하고 싶은 것은 이것과 같습니다. (주로 mvvm과 함께 viewmodel 첫 번째 접근 방식을 사용합니다)

대화창을 호출하는 ViewModel :

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result

어떻게 작동합니까?

먼저 대화 서비스를 만들었습니다.

public interface IUIWindowDialogService
{
    bool? ShowDialog(string title, object datacontext);
}

public class WpfUIWindowDialogService : IUIWindowDialogService
{
    public bool? ShowDialog(string title, object datacontext)
    {
        var win = new WindowDialog();
        win.Title = title;
        win.DataContext = datacontext;

        return win.ShowDialog();
    }
}

WindowDialog특별하지만 간단한 창입니다. 내 콘텐츠를 보유하려면 필요합니다.

<Window x:Class="WindowDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Title="WindowDialog" 
    WindowStyle="SingleBorderWindow" 
    WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
    <ContentPresenter x:Name="DialogPresenter" Content="{Binding .}">

    </ContentPresenter>
</Window>

wpf의 대화 상자 문제 dialogresult = true는 코드에서만 가능하다는 것입니다. 그렇기 때문에 dialogviewmodel구현할 인터페이스를 만들었 습니다.

public class RequestCloseDialogEventArgs : EventArgs
{
    public bool DialogResult { get; set; }
    public RequestCloseDialogEventArgs(bool dialogresult)
    {
        this.DialogResult = dialogresult;
    }
}

public interface IDialogResultVMHelper
{
    event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
}

내 ViewModel이 시간이라고 생각할 때마다이 dialogresult = true이벤트를 발생시킵니다.

public partial class DialogWindow : Window
{
    // Note: If the window is closed, it has no DialogResult
    private bool _isClosed = false;

    public DialogWindow()
    {
        InitializeComponent();
        this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged;
        this.Closed += DialogWindowClosed;
    }

    void DialogWindowClosed(object sender, EventArgs e)
    {
        this._isClosed = true;
    }

    private void DialogPresenterDataContextChanged(object sender,
                              DependencyPropertyChangedEventArgs e)
    {
        var d = e.NewValue as IDialogResultVMHelper;

        if (d == null)
            return;

        d.RequestCloseDialog += new EventHandler<RequestCloseDialogEventArgs>
                                    (DialogResultTrueEvent).MakeWeak(
                                        eh => d.RequestCloseDialog -= eh;);
    }

    private void DialogResultTrueEvent(object sender, 
                              RequestCloseDialogEventArgs eventargs)
    {
        // Important: Do not set DialogResult for a closed window
        // GC clears windows anyways and with MakeWeak it
        // closes out with IDialogResultVMHelper
        if(_isClosed) return;

        this.DialogResult = eventargs.DialogResult;
    }
 }

이제 적어도 DataTemplate내 리소스 파일 ( app.xaml또는 무언가) 을 만들어야합니다 .

<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
        <DialogView:EditOrNewAuswahlItem/>
</DataTemplate>

자, 이제 뷰 모델에서 대화 상자를 호출 할 수 있습니다.

 var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);

이제 내 질문에,이 솔루션에 문제가 있습니까?

편집 : 완전성을 위해. ViewModel은 구현해야 IDialogResultVMHelper하며 다음과 같이 만들 수 있습니다 OkCommand.

public class MyViewmodel : IDialogResultVMHelper
{
    private readonly Lazy<DelegateCommand> _okCommand;

    public MyViewmodel()
    {
         this._okCommand = new Lazy<DelegateCommand>(() => 
             new DelegateCommand(() => 
                 InvokeRequestCloseDialog(
                     new RequestCloseDialogEventArgs(true)), () => 
                         YourConditionsGoesHere = true));
    }

    public ICommand OkCommand
    { 
        get { return this._okCommand.Value; } 
    }

    public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
    private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
    {
        var handler = RequestCloseDialog;
        if (handler != null) 
            handler(this, e);
    }
 }

편집 2 : 내 이벤트 핸들러가 약한 등록하기 위해 여기에서 코드를 사용 :
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(웹 사이트가 더 이상 존재하지 않는, WebArchive 미러 )

public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler) 
    where TE : EventArgs;

public interface IWeakEventHandler<TE> 
    where TE : EventArgs
{
    EventHandler<TE> Handler { get; }
}

public class WeakEventHandler<T, TE> : IWeakEventHandler<TE> 
    where T : class 
    where TE : EventArgs
{
    private delegate void OpenEventHandler(T @this, object sender, TE e);

    private readonly WeakReference mTargetRef;
    private readonly OpenEventHandler mOpenHandler;
    private readonly EventHandler<TE> mHandler;
    private UnregisterCallback<TE> mUnregister;

    public WeakEventHandler(EventHandler<TE> eventHandler,
                                UnregisterCallback<TE> unregister)
    {
        mTargetRef = new WeakReference(eventHandler.Target);

        mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate(
                           typeof(OpenEventHandler),null, eventHandler.Method);

        mHandler = Invoke;
        mUnregister = unregister;
    }

    public void Invoke(object sender, TE e)
    {
        T target = (T)mTargetRef.Target;

        if (target != null)
            mOpenHandler.Invoke(target, sender, e);
        else if (mUnregister != null)
        {
            mUnregister(mHandler);
            mUnregister = null;
        }
    }

    public EventHandler<TE> Handler
    {
        get { return mHandler; }
    }

    public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh)
    {
        return weh.mHandler;
    }
}

public static class EventHandlerUtils
{
    public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler, 
                                                    UnregisterCallback<TE> unregister)
        where TE : EventArgs
    {
        if (eventHandler == null)
            throw new ArgumentNullException("eventHandler");

        if (eventHandler.Method.IsStatic || eventHandler.Target == null)
            throw new ArgumentException("Only instance methods are supported.",
                                            "eventHandler");

        var wehType = typeof(WeakEventHandler<,>).MakeGenericType(
                          eventHandler.Method.DeclaringType, typeof(TE));

        var wehConstructor = wehType.GetConstructor(new Type[] 
                             { 
                                 typeof(EventHandler<TE>), typeof(UnregisterCallback<TE>) 
                             });

        IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
                                        new object[] { eventHandler, unregister });

        return weh.Handler;
    }
}

1
WindowDialog XAML에서 xmlns : x = " schemas.microsoft.com/winfx/2006/xaml "참조가 누락되었을 수 있습니다 .
Adiel Yaacov

실제로 네임 스페이스의 xmlns : x이고 = "[HTTP : //] schemas.microsoft.com/winfx/2006/xaml"괄호없이
reggaeguitar


1
안녕하세요! Latecomer입니다. 귀하의 서비스가 WindowDialog에 대한 참조를 갖는 방법을 이해하지 못합니다. 모델의 계층 구조는 무엇입니까? 내 생각에, View는 Viewmodel 어셈블리에 대한 참조를 보유하고 Viewmodel은 서비스 및 모델 어셈블리에 대한 참조를 보유합니다. 따라서 서비스 계층에는 WindowDialog보기에 대한 지식이 없습니다. 내가 무엇을 놓치고 있습니까?
Moe45673

2
안녕하세요 @blindmeis,이 개념을 머리로 감싸려고 노력하고 있습니다. 선택할 수있는 온라인 예제 프로젝트가 있다고 생각하지 않습니까? 내가 혼란스럽게하는 많은 것들이 있습니다.
Hank

답변:


48

이것은 좋은 접근 방법이며 과거에도 비슷한 방법을 사용했습니다. 해봐!

내가 분명히 할 한 가지 작은 일은 DialogResult에서 "false"를 설정해야 할 때 이벤트가 부울을 받도록하는 것입니다.

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

그리고 EventArgs 클래스 :

public class RequestCloseEventArgs : EventArgs
{
    public RequestCloseEventArgs(bool dialogResult)
    {
        this.DialogResult = dialogResult;
    }

    public bool DialogResult { get; private set; }
}

서비스를 사용하는 대신 일종의 콜백을 사용하여 ViewModel 및 View와의 상호 작용을 용이하게하려면 어떻게해야합니까? 예를 들어, View는 ViewModel에서 Command를 실행 한 다음, 모든 사항을 말하고 완료하면 ViewModel이 View의 콜백을 실행하여 Command의 결과를 표시합니다. 서비스를 사용하여 ViewModel에서 대화 상자 상호 작용을 처리하는 데 여전히 팀을 참여시킬 수 없습니다.
Matthew S

15

나는 몇 달 동안 거의 동일한 접근 방식을 사용 해왔으며 매우 행복합니다 (즉, 아직 완전히 다시 작성하려는 충동을 느끼지 못했습니다 ...)

내 구현에서는 IDialogViewModel제목, 모든 대화 상자에서 일관된 모양을 나타 내기 위해 표시되는 표준 버튼, RequestClose이벤트 및 창 크기를 제어 할 수있는 몇 가지 다른 항목을 노출시키는를 사용합니다. 행동


thx, 제목은 실제로 내 IDialogViewModel에 있어야합니다. 크기, 표준 버튼 i와 같은 다른 속성은 적어도 데이터 템플릿에서 가져 오기 때문에 떠나게됩니다.
blindmeis

1
저도 처음에 한 일입니다. SizeToContent를 사용하여 창의 크기를 제어하십시오. 그러나 어떤 경우에는 창 크기를 조정해야했기 때문에 조금 조정해야했습니다.
Thomas Levesque

@ThomasViewModel에 포함 된 버튼을 남겨 두십시오. 실제로 UI 버튼 객체입니까, 버튼을 나타내는 객체입니까?
토마스

3
@Thomas, 버튼을 나타내는 객체. ViewModel에서 UI 객체를 참조해서는 안됩니다.
Thomas Levesque

2

팝업 메시지 상자가 아닌 대화 창에 대해 이야기하는 경우 아래의 접근 방식을 고려하십시오. 핵심 사항은 다음과 같습니다.

  1. Module Controller각 생성자에 대한 참조를 전달합니다.ViewModel (주입을 사용할 수 있음).
  2. Module Controller(결과를 반환하지 않고, 단지 생성) 대화 창을 만들기위한 공공 / 내부 메소드가 있습니다. 따라서 대화 창을 열려면ViewModel 작성하십시오.controller.OpenDialogEntity(bla, bla...)
  3. 각 대화 창은 약한 이벤트 를 통해 결과 (예 : 확인 , 저장 , 취소 등)를 알려줍니다 . PRISM을 사용 하면이 EventAggregator를 사용하여 알림을 쉽게 게시 할 수 있습니다. 있습니다.
  4. 대화 결과를 처리하기 위해 알림 구독을 사용하고 있습니다 ( PRISM의 경우 약한 이벤트EventAggregator ). 이러한 알림에 대한 종속성을 줄이려면 표준 알림과 함께 독립 클래스를 사용하십시오.

장점 :

  • 적은 코드. 인터페이스를 사용하는 것은 마음에 들지 않지만 인터페이스와 추상화 계층을 과도하게 사용하면 도움말보다 많은 문제가 발생하는 프로젝트가 너무 많습니다.
  • 열린 대화 창 Module Controller은 강력한 참조를 피하고 테스트를 위해 모형을 사용할 수있는 간단한 방법입니다.
  • 약한 이벤트를 통한 알림은 잠재적 인 메모리 누수 수를 줄입니다.

단점 :

  • 처리기에서 다른 사용자와 필요한 알림을 쉽게 구분할 수 없습니다. 두 가지 솔루션 :
    • 대화 창을 열 때 고유 토큰을 보내고 구독에서 해당 토큰을 확인하십시오.
    • 일반적인 알림 클래스 사용 기관의 열거입니다 (또는 단순 것이 뷰 모델의 유형이 될 수 있습니다).<T>T
  • 프로젝트의 경우 알림 클래스를 사용하여 알림 클래스를 복제하는 것을 방지하는 데 동의해야합니다.
  • 방대한 규모의 프로젝트의 경우 Module Controller창을 만드는 방법이 압도적 일 수 있습니다. 이 경우 여러 모듈로 나누는 것이 좋습니다.

추신 : 나는이 접근법을 꽤 오랫동안 사용 해 왔으며 의견에서 적격성을 방어하고 필요한 경우 몇 가지 예를 제공 할 준비가되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.