MVVM ile wpf'de Diyaloglar için iyi mi yoksa kötü bir uygulama mı?


149

Son zamanlarda wpf uygulamam için ekleme ve düzenleme iletişim kutuları oluşturma konusunda sorun yaşadım.

Kodumda tek yapmak istediğim bunun gibi bir şeydi. (Mvvm ​​ile çoğunlukla viewmodel ilk yaklaşımını kullanıyorum)

Bir iletişim penceresi çağıran ViewModel:

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

O nasıl çalışır?

İlk önce bir diyalog servisi oluşturdum:

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özel ama basit bir penceredir. İçeriğimi tutması için ona ihtiyacım var:

<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'deki diyaloglarla ilgili bir sorun dialogresult = true, yalnızca kodla elde edilebilir. Bu yüzden dialogviewmodeluygulayabileceğim bir arayüz oluşturdum .

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

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

ViewModel'im zamanının geldiğini düşündüğünde dialogresult = true, bu etkinliği gündeme getirin.

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

Şimdi en azından DataTemplatekaynak dosyamda ( app.xamlveya başka bir şeyde) bir oluşturmam gerekiyor:

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

Hepsi bu kadar, artık görünüm modellerimden diyalog çağırabilirim:

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

Şimdi sorum, bu çözümle ilgili herhangi bir sorun görüyor musunuz?

Düzenleme: eksiksizlik için. ViewModel uygulamalı IDialogResultVMHelperve sonra onu a OkCommandveya bunun gibi bir şey içinde yükseltebilir :

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

DÜZENLEME 2: EventHandler kaydımı zayıflatmak için buradan kodu kullandım:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Web sitesi artık mevcut değil, 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;
    }
}

1
WindowDialog XAML'nizde muhtemelen xmlns: x = " schemas.microsoft.com/winfx/2006/xaml " referansını kaçırıyorsunuz.
Adiel Yaacov

Aslında ad alanı, parantezler olmadan xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" şeklindedir
reggaeguitar


1
Selam! Buraya geç gel. Hizmetinizin WindowDialog'a nasıl bir referansı olduğunu anlamıyorum. Modellerinizin hiyerarşisi nedir? Aklımda, View Viewmodel montajına ve Viewmodel Servis ve Model montajlarına bir referans tutar. Böylece, Hizmet katmanı WindowDialog görünümü hakkında bilgi sahibi olmayacaktır. Neyi kaçırıyorum?
Moe45673

2
Merhaba @blindmeis, sadece kafamı bu konsepte sarmaya çalışıyorum, seçebileceğim bir çevrimiçi örnek proje olduğunu sanmıyorum? Kafam karışan birkaç şey var.
Hank

Yanıtlar:


48

Bu iyi bir yaklaşım ve geçmişte benzerlerini kullandım. Göreyim seni!

Kesinlikle yapacağım küçük bir şey, DialogResult'da "false" ayarlamanız gerektiğinde olayın bir boole almasını sağlamaktır.

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

ve EventArgs sınıfı:

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

    public bool DialogResult { get; private set; }
}

Hizmetleri kullanmak yerine ViewModel ve View ile etkileşimi kolaylaştırmak için bir tür Geri Arama kullanılırsa ne olur? Örneğin, View, ViewModel'de bir Komutu yürütür, ardından her şey söylendiğinde ve yapıldığında, ViewModel, Komutun sonuçlarını görüntülemek için Görünüm için bir Geri Çağırma başlatır. ViewModel'de Dialog etkileşimlerini yönetmek için Hizmetleri kullanarak ekibimi hâlâ gemiye alamıyorum.
Matthew S

15

Birkaç aydır neredeyse aynı yaklaşımı kullanıyorum ve bundan çok memnunum (yani henüz tamamen yeniden yazma isteğini hissetmedim ...)

Uygulamamda, pencere boyutunu kontrol edebilmek için IDialogViewModelbaşlık, gösterilecek standad düğmeleri (tüm iletişim kutularında tutarlı bir görünüme sahip olmak için), bir RequestCloseolay ve birkaç başka şey gibi şeyleri ortaya çıkaran bir kullanıyorum. davranış


teşekkürler, başlık gerçekten benim IDialogViewModel'ime girmeli. boyut, standart düğme gibi diğer özellikler bırakacağım, çünkü bunların hepsi en azından veri kalıbından geliyor.
blindmeis

1
İlk başta ben de öyle yaptım, sadece pencerenin boyutunu kontrol etmek için SizeToContent'i kullanın. Ama bir durumda pencereyi yeniden boyutlandırmam gerekiyordu, bu yüzden biraz
ince

@ThomasLevesque ViewModel'inizde bulunan düğmeler, aslında UI Button nesneleri mi yoksa düğmeleri temsil eden nesneler mi?
Thomas

3
@Thomas, düğmeleri temsil eden nesneler. ViewModel'de hiçbir zaman UI nesnelerine başvurmamalısınız.
Thomas Levesque

2

Diyalog pencerelerinden bahsediyorsanız ve sadece açılır mesaj kutularından bahsetmiyorsanız, lütfen aşağıdaki yaklaşımımı düşünün. Kilit noktalar şunlardır:

  1. Module ControllerHer birinin yapıcısına bir referans iletiyorum ViewModel(enjeksiyon kullanabilirsiniz).
  2. Bu Module Controller, diyalog pencereleri oluşturmak için genel / dahili yöntemlere sahiptir (bir sonuç döndürmeden sadece oluşturmak). Dolayısıyla ViewModelyazıyorum bir diyalog penceresi açmak için :controller.OpenDialogEntity(bla, bla...)
  3. Her diyalog penceresi , Zayıf Olaylar aracılığıyla sonucunu ( Tamam , Kaydet , İptal vb.) Bildirir . PRISM kullanıyorsanız, bu EventAggregator'ı kullanarak bildirim yayınlamak daha kolaydır .
  4. Diyalog sonuçlarını işlemek için bildirimlere abonelik kullanıyorum (yine Zayıf Olaylar ve PRISM durumunda EventAggregator ). Bu tür bildirimlere bağımlılığı azaltmak için, standart bildirimlerle bağımsız sınıflar kullanın.

Artıları:

  • Daha az kod. Arayüz kullanmayı umursamıyorum, ancak arayüz ve soyutlama katmanlarının aşırı kullanımının yardımdan çok soruna neden olduğu çok fazla proje gördüm.
  • Açık diyalog pencereleri Module Controllergüçlü referanslardan kaçınmanın basit bir yoludur ve yine de test için modellerin kullanılmasına izin verir.
  • Zayıf olaylar yoluyla bildirim, olası bellek sızıntılarının sayısını azaltır.

Eksileri:

  • İşleyicide gerekli bildirimi diğerlerinden ayırt etmek kolay değil. İki çözüm:
    • bir diyalog penceresi açıldığında benzersiz bir jeton gönderin ve abonelikte bu jetonu kontrol edin
    • genel bildirim sınıfları kullanmak birimlerin numaralandırma (veya basitlik için ViewModel tipi olabilir).<T>T
  • Bir proje için, çoğaltılmasını önlemek için bildirim sınıflarını kullanma konusunda bir anlaşma olmalıdır.
  • Çok büyük projeler için Module Controller, pencere oluşturma yöntemleriyle bunaltılabilir. Bu durumda, onu birkaç modüle bölmek daha iyidir.

Not: Bu yaklaşımı uzun zamandır kullanıyorum ve yorumlarda uygunluğunu savunmaya ve gerekirse bazı örnekler vermeye hazırım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.