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>

खैर सब, मैं अब अपने viewmodels से संवाद कॉल कर सकते हैं:

 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/SolveTheProblemWithEventsWeakEventHandlers.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;
    }
}

1
आप शायद अपने WindowDialog XAML में xmlns: x = " schemas.microsoft.com/winfx/2006/xaml " रेफ़रेन्स को याद कर रहे हैं ।
अडियल याकोव

वास्तव में नामस्थान xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" कोष्ठक के बिना
reggaeguitar


1
नमस्ते! यहां पर लेट आने वाला। मुझे समझ नहीं आ रहा है कि आपकी सेवा का WindowDialog का संदर्भ कैसे है। आपके मॉडलों का पदानुक्रम क्या है? मेरे दिमाग में, व्यू सर्विस और मॉडल असेंबली के लिए व्यूमॉडल असेंबली और व्यूमॉडल का संदर्भ रखता है। जिससे, सेवा परत को WindowDialog दृश्य का कोई ज्ञान नहीं होगा। मैं क्या खो रहा हूँ?
Moe45673

2
हाय @blindmeis, बस इस अवधारणा के आसपास मेरे सिर को लपेटने की कोशिश कर रहा है, मुझे नहीं लगता कि कुछ ऑनलाइन उदाहरण परियोजना है जिसे मैं चुन सकता हूं? ऐसी कई चीजें हैं जिनके बारे में मुझे भ्रम है।
हांक

जवाबों:


48

यह एक अच्छा दृष्टिकोण है और मैंने अतीत में ऐसे ही लोगों का इस्तेमाल किया था। इसका लाभ उठाएं!

एक छोटी सी बात जो मैं निश्चित रूप से करूंगा, वह यह है कि जब आप को डायलॉगस्कूल में "झूठा" सेट करने की आवश्यकता हो, तो ईवेंट को एक बूलियन प्राप्त होगा।

event EventHandler<RequestCloseEventArgs> RequestCloseDialog;

और EventArgs वर्ग:

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

    public bool DialogResult { get; private set; }
}

क्या होगा अगर सेवाओं का उपयोग करने के बजाय, कोई ViewModel और दृश्य के साथ बातचीत को सुविधाजनक बनाने के लिए कॉलबैक का उपयोग करता है? उदाहरण के लिए, ViewModel में एक कमांड निष्पादित करता है, फिर जब सभी कहा और किया जाता है, तो ViewModel कमांड के परिणामों को प्रदर्शित करने के लिए व्यू के लिए एक कॉलबैक फायर करता है। मैं अभी भी ViewModel में डायलॉग इंटरैक्शन को संभालने के लिए सेवाओं का उपयोग करके अपनी टीम को ऑनबोर्ड नहीं कर सकता।
मैथ्यू एस

15

मैं अब कई महीनों के लिए लगभग समान दृष्टिकोण का उपयोग कर रहा हूं, और मैं इससे बहुत खुश हूं (यानी मैंने अभी तक इसे पूरी तरह से फिर से लिखने का आग्रह नहीं किया है ...)

मेरे कार्यान्वयन में, मैं एक IDialogViewModelऐसी चीज़ का उपयोग करता हूं, जैसे शीर्षक, स्टैंडड बटन दिखाने के लिए (ताकि सभी संवादों में एक सुसंगत स्पष्टता हो), एक RequestCloseघटना और खिड़की के आकार को नियंत्रित करने में सक्षम होने के लिए कुछ अन्य चीजें और व्यवहार


thx, शीर्षक वास्तव में मेरे IDialogViewModel में जाना चाहिए। अन्य गुण जैसे आकार, मानक बटन मैं छोड़ दूंगा, क्योंकि यह सब डेटेटप्लेट से कम से कम आता है।
अंधविश्वासी

1
यही मैंने पहले भी किया था, बस Window के आकार को नियंत्रित करने के लिए SizeToContent का उपयोग करें। लेकिन एक मामले में मुझे खिड़की को फिर से जीवंत बनाने की आवश्यकता थी, इसलिए मुझे इसे थोड़ा मोड़ना पड़ा ...
थॉमस लेवेस्क

@ThomasLevesque आपके ViewModel में निहित बटन, क्या वे वास्तव में UI बटन ऑब्जेक्ट्स या बटन का प्रतिनिधित्व करने वाले ऑब्जेक्ट हैं?
थॉमस

3
@ थोमस, बटन का प्रतिनिधित्व करने वाली वस्तुएं। आपको ViewModel में UI ऑब्जेक्ट्स का संदर्भ नहीं देना चाहिए।
थॉमस लेवेस्क

2

यदि आप संवाद विंडो के बारे में बात कर रहे हैं, न कि केवल पॉप-अप संदेश बॉक्स के बारे में, तो कृपया नीचे दिए गए मेरे दृष्टिकोण पर विचार करें। प्रमुख बिंदु हैं:

  1. मैं Module Controllerप्रत्येक के कंस्ट्रक्टर में एक संदर्भ देता हूं ViewModel(आप इंजेक्शन का उपयोग कर सकते हैं)।
  2. यही कारण है कि Module Controllerबातचीत खिड़कियां बनाने (बस बनाने, एक परिणाम के लौटने के बिना) के लिए सार्वजनिक / आंतरिक तरीकों है। इसलिए ViewModelमैं लिखने के लिए एक संवाद खिड़की खोलने के लिए :controller.OpenDialogEntity(bla, bla...)
  3. प्रत्येक ईवेंट विंडो अपने परिणामों के बारे में सूचित करता है (जैसे ठीक है , सहेजें , रद्द करें आदि) कमजोर घटनाओं के माध्यम से । यदि आप PRISM का उपयोग करते हैं, तो इस EventAggregator का उपयोग करके सूचनाएं प्रकाशित करना आसान है ।
  4. संवाद परिणामों को संभालने के लिए, मैं सूचनाओं के लिए सदस्यता का उपयोग कर रहा हूं (फिर PRISM के मामले में कमजोर घटनाक्रम और EventAggregator )। ऐसी सूचनाओं पर निर्भरता कम करने के लिए, मानक सूचनाओं के साथ स्वतंत्र कक्षाओं का उपयोग करें।

पेशेवरों:

  • कम कोड। मुझे इंटरफेस का उपयोग करने में कोई आपत्ति नहीं है, लेकिन मैंने बहुत सी परियोजनाएँ देखी हैं जहाँ इंटरफेस और एब्स्ट्रक्शन लेयर्स का उपयोग करने की अधिकता मदद से अधिक परेशानी का कारण बनती है।
  • के माध्यम से खुले संवाद खिड़कियां Module Controllerमजबूत संदर्भों से बचने का एक सरल तरीका है और अभी भी परीक्षण के लिए मॉक-अप का उपयोग करने की अनुमति देता है।
  • कमजोर घटनाओं के माध्यम से अधिसूचना संभावित मेमोरी लीक की संख्या को कम करती है।

विपक्ष:

  • हैंडलर में दूसरों से आवश्यक अधिसूचना को अलग करना आसान नहीं है। दो समाधान:
    • एक संवाद विंडो खोलने पर एक अद्वितीय टोकन भेजें और सदस्यता में उस टोकन की जांच करें
    • सामान्य अधिसूचना वर्गों का उपयोग करें <T>जहां Tसंस्थाओं की गणना है (या सरलता के लिए यह ViewModel का प्रकार हो सकता है)।
  • एक परियोजना के लिए उन्हें नकल करने से रोकने के लिए अधिसूचना वर्गों का उपयोग करने के बारे में एक समझौता होना चाहिए।
  • बहुत बड़ी परियोजनाओं के Module Controllerलिए खिड़कियां बनाने के तरीकों से अभिभूत हो सकते हैं। इस मामले में इसे कई मॉड्यूल में विभाजित करना बेहतर है।

पी एस मैं इस दृष्टिकोण का उपयोग काफी लंबे समय से कर रहा हूं और टिप्पणियों में इसकी पात्रता की रक्षा करने के लिए तैयार है और यदि आवश्यक हो तो कुछ उदाहरण प्रदान करें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.