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

최근에 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;
    }
}



답변

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

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

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

그리고 EventArgs 클래스 :

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

    public bool DialogResult { get; private set; }
}


답변

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

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


답변

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

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

장점 :

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

단점 :

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

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


답변