IT

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

lottoking 2020. 6. 18. 07:34
반응형

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 : 여기에서 코드를 사용하여 EventHandler 레지스터를 약하게 만들었습니다 :
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(웹 사이트가 더 이상 존재하지 않음, WebArchive Mirror )

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

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

In my implementation, I use a IDialogViewModel that exposes things such as the title, the standad buttons to show (in order to have a consistent apparence across all dialogs), a RequestClose event, and a few other things to be able to control the window size and behavior


If you are talking about dialogue windows and not just about the pop-up message boxes, please consider my approach below. The key points are:

  1. I pass a reference to Module Controller into the constructor of each ViewModel (you can use injection).
  2. That Module Controller has public/internal methods for creating dialogue windows (just creating, without returning a result). Hence to open a dialogue window in ViewModel I write: controller.OpenDialogEntity(bla, bla...)
  3. Each dialogue window notifies about its result (like OK, Save, Cancel, etc.) via Weak Events. If you use PRISM, then it's easier to publish notifications using this EventAggregator.
  4. To handle dialogue results, I'm using subscription to notifications (again Weak Events and EventAggregator in case of PRISM). To reduce dependency on such notifications, use independent classes with standard notifications.

Pros:

  • Less code. I don't mind using interfaces, but I've seen too many projects where excessiveness of using interfaces and abstraction layers cause more trouble than help.
  • Open dialogue windows through Module Controller is a simple way to avoid strong references and still allows to use mock-ups for testing.
  • Notification through weak events reduce number of potential memory leaks.

Cons:

  • Not easy to distinguish required notification from others in the handler. Two solutions:
    • send a unique token on opening a dialogue window and check that token in the subscription
    • use generic notification classes <T> where T is enumeration of entities (or for simplicity it can be type of ViewModel).
  • For a project should be an agreement about using notification classes to prevent duplicating them.
  • For enormously large projects the Module Controller can be overwhelmed by methods for creating windows. In this case it's better to split it up in several modules.

P.S. I have been using this approach for quite a long time now and ready to defend its eligibility in comments and provide some examples if required.

참고URL : https://stackoverflow.com/questions/3801681/good-or-bad-practice-for-dialogs-in-wpf-with-mvvm

반응형