2010-09-27 10 views
127

मुझे हाल ही में मेरे wpf ऐप के लिए संवाद जोड़ने और संपादित करने की समस्या थी।एमवीवीएम के साथ डब्ल्यूपीएफ में संवाद के लिए अच्छा या बुरा अभ्यास?

मैं अपने कोड में ऐसा करना चाहता था, ऐसा कुछ ऐसा था।

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

यह कैसे काम करता:

ViewModel जो एक संवाद खिड़की कॉल (मैं ज्यादातर MVVM साथ viewmodel पहले दृष्टिकोण का उपयोग)?

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 के लिए समय है, तो इस घटना को बढ़ा।

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); 
: अब मैं अपने ViewModels से संवाद कॉल कर सकते हैं

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

खैर सभी thats,:

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 या कुछ और) बनाने के लिए

अब मेरा प्रश्न, क्या आपको इस समाधान के साथ कोई समस्या है?

संपादित करें: पूर्णता के लिए। 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
(वेबसाइट नहीं: मैं अपने eventhandler कमजोर रजिस्टर बनाने के लिए यहां से कोड का इस्तेमाल किया लंबे समय तक मौजूद है, 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

आप शायद xmlns गायब हैं: x = "http://schemas.microsoft.com/winfx/2006/xaml" अपने विंडोडिअलॉग XAML में refernece। –

+0

वास्तव में नामस्थान xmlns है: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" ब्रैकेट्स के बिना – reggaeguitar

+0

देखें http://stackoverflow.com/questions/16993433/mvvm-light-wpf -बाइंडिंग-बहु-उदाहरण-के-एक-विंडो-टू-ए-व्यूमोडेल/16 994523 # 16 994523 – reggaeguitar

उत्तर

43

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

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

event EventHandler<RequestCloseEventArgs> RequestCloseDialog; 

और EventArgs वर्ग:

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

    public bool DialogResult { get; private set; } 
} 
+0

thx, मैं अपना ईवेंट बदलूंगा :) – blindmeis

+7

मुझे लगता है कि, 'बूल' के बजाय वहां एक कस्टम EventArgs होना चाहिए जो 'EventArgs' श्रेणी' से प्राप्त होता है जिसमें 'बूल' संपत्ति होती है। 'EventHandler' प्रतिनिधि के पास सामान्य पैरामीटर पर एक वर्ग बाधा है जिसके लिए' EventArgs' से प्राप्त प्रकार की आवश्यकता होती है। जेनेरिक पैरामीटर के रूप में 'बूल' के साथ यह संकलित नहीं होता है (कम से कम वीएस -2010 में नहीं, मुझे नहीं पता कि यह संभवतः पिछले संस्करणों से बदल गया है)। – Slauma

+0

आप सही हैं, मैंने नमूना कोड तय किया है। धन्यवाद –

15

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

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

+0

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

+1

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

+0

mhh thx इस जानकारी के लिए :) – blindmeis

2

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

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

सकारात्मक:

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

विपक्ष:

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

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

संबंधित मुद्दे