2008-10-13 10 views
29

मेरे पास एक तृतीय-पक्ष संपादक है जिसमें मूल रूप से एक टेक्स्टबॉक्स और एक बटन (DevExpress बटन संपादन नियंत्रण) शामिल है। मैं एक विशेष कीस्ट्रोक बनाना चाहता हूं (Alt + नीचे) बटन पर क्लिक अनुकरण करना चाहते हैं। इस बार और अधिक लिखने से बचने के लिए, मैं एक जेनेरिक कीप इवेंट हैंडलर बनाना चाहता हूं जो बटनक्लिक घटना को बढ़ाएगा। दुर्भाग्य से, बटनक्लिक घटना को नियंत्रित करने वाले नियंत्रण में कोई विधि प्रतीत नहीं होती है, इसलिए ...मैं .NET/C# में प्रतिबिंब के माध्यम से एक ईवेंट कैसे बढ़ा सकता हूं?

मैं प्रतिबिंब के माध्यम से बाहरी फ़ंक्शन से ईवेंट कैसे बढ़ा सकता हूं?

उत्तर

34

यहाँ जेनरिक का उपयोग कर (त्रुटि जाँच छोड़े गए) एक डेमो है:

using System; 
using System.Reflection; 
static class Program { 
    private class Sub { 
    public event EventHandler<EventArgs> SomethingHappening; 
    } 
    internal static void Raise<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs 
    { 
    var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source); 
    if (eventDelegate != null) 
    { 
     foreach (var handler in eventDelegate.GetInvocationList()) 
     { 
     handler.Method.Invoke(handler.Target, new object[] { source, eventArgs }); 
     } 
    } 
    } 
    public static void Main() 
    { 
    var p = new Sub(); 
    p.Raise("SomethingHappening", EventArgs.Empty); 
    p.SomethingHappening += (o, e) => Console.WriteLine("Foo!"); 
    p.Raise("SomethingHappening", EventArgs.Empty); 
    p.SomethingHappening += (o, e) => Console.WriteLine("Bar!"); 
    p.Raise("SomethingHappening", EventArgs.Empty); 
    Console.ReadLine(); 
    } 
} 
+2

मैंने देखा! आपने एक eventInfo स्थानीय चर बनाया है, लेकिन आप इसके साथ कुछ भी नहीं करते हैं। क्या यह सिर्फ मुझे है या पहली पंक्ति को हटाया जा सकता है? – Nick

+1

आपको eventInfo प्राप्त करने की आवश्यकता नहीं है। यह बेकार है। अन्यथा, अच्छा नमूना! – henon

+3

I ऐसा नहीं लगता कि GetInvocationList() को कॉल करना जरूरी है। बस 'eventDelegate' का उपयोग करना पर्याप्त है। – HappyNomad

5
Raising an event via reflection से

, हालांकि मुझे VB.NET में जवाब लगता है, वह यह है कि, यह एक के आगे दो पदों (उदाहरण के लिए, मैं पर प्रेरणा के लिए VB.NET एक के लिए लग रही होगी सामान्य दृष्टिकोण के साथ प्रदान करेगा एक वर्ग में एक प्रकार का संदर्भ नहीं देना):

public event EventHandler<EventArgs> MyEventToBeFired; 

    public void FireEvent(Guid instanceId, string handler) 
    { 

     // Note: this is being fired from a method with in the same 
     //  class that defined the event (that is, "this"). 

     EventArgs e = new EventArgs(instanceId); 

     MulticastDelegate eventDelagate = 
       (MulticastDelegate)this.GetType().GetField(handler, 
       System.Reflection.BindingFlags.Instance | 
       System.Reflection.BindingFlags.NonPublic).GetValue(this); 

     Delegate[] delegates = eventDelagate.GetInvocationList(); 

     foreach (Delegate dlg in delegates) 
     { 
      dlg.Method.Invoke(dlg.Target, new object[] { this, e }); 
     } 
    } 

    FireEvent(new Guid(), "MyEventToBeFired"); 
12

आप आमतौर पर अन्य कक्षाओं की घटनाओं को नहीं बढ़ा सकते हैं। घटनाक्रम वास्तव में एक निजी प्रतिनिधि क्षेत्र के रूप में संग्रहीत होते हैं, साथ ही दो एक्सेसर्स (add_event और remove_event)।

प्रतिबिंब के माध्यम से इसे करने के लिए, आपको बस निजी प्रतिनिधि क्षेत्र को ढूंढने, इसे प्राप्त करने, फिर उसे आमंत्रित करने की आवश्यकता है।

13

सामान्य रूप से, आप नहीं कर सकते। घटनाओं के बारे में सोचें मूल रूप से AddHandler/RemoveHandler विधियों के जोड़े (जैसा कि मूल रूप से वे क्या हैं)। उन्हें कैसे लागू किया जाता है कक्षा तक है। अधिकांश WinForms उनके कार्यान्वयन के रूप में EventHandlerList का उपयोग नियंत्रित करते हैं, लेकिन यदि आपका निजी क्षेत्र और चाबियाँ लेना शुरू होता है तो आपका कोड बहुत भंगुर होगा।

क्या ButtonEdit नियंत्रण OnClick विधि का पर्दाफाश करता है जिसे आप कॉल कर सकते हैं?

फुटनोट: वास्तव में, घटनाएं सदस्यों को "उठाएं" है, इसलिए EventInfo.GetRaiseMethod। हालांकि, यह कभी सी # द्वारा पॉप्युलेट नहीं किया जाता है और मुझे विश्वास नहीं है कि यह सामान्य रूप से ढांचे में है।

6

यह पता चला है के रूप में, मैं यह कर सकता है और यह एहसास नहीं था:

buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down); 

लेकिन अगर मैं मैं स्रोत कोड में तल्लीन और विधि को जन्म देती है खोजने के लिए किया है होता नहीं कर सका घटना।

सहायता के लिए धन्यवाद, सब कुछ।

5

यदि आप जानते हैं कि नियंत्रण एक बटन है तो आप इसे PerformClick() विधि पर कॉल कर सकते हैं। मुझे OnEnter, OnExit जैसी अन्य घटनाओं के लिए समान समस्या है। अगर मैं प्रत्येक नियंत्रण प्रकार के लिए एक नया प्रकार नहीं प्राप्त करना चाहता हूं तो मैं उन घटनाओं को नहीं बढ़ा सकता।

+0

'पर क्लिक करें निष्पादित करें' तुम मुझसे मजाक कर रहे हो गया है, यह शानदार है –

8

मैं कक्षाओं के लिए एक विस्तार है, जो RaisePropertyChange < टी> विधि सुई INotifyPropertyChanged लागू करता है लिखा था, इसलिए मैं इसे इस तरह का उपयोग कर सकते हैं:

this.RaisePropertyChanged(() => MyProperty); 

किसी भी बेस क्लास में विधि को लागू किए बिना। मेरे उपयोग के लिए यह धीमा करना था, लेकिन शायद स्रोत कोड किसी की मदद कर सकता है।

तो यहाँ यह है:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Diagnostics; 
using System.Linq.Expressions; 
using System.Reflection; 
using System.Globalization; 

namespace Infrastructure 
{ 
    /// <summary> 
    /// Adds a RaisePropertyChanged method to objects implementing INotifyPropertyChanged. 
    /// </summary> 
    public static class NotifyPropertyChangeExtension 
    { 
     #region private fields 

     private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache = new Dictionary<string, PropertyChangedEventArgs>(); 
     private static readonly object syncLock = new object(); 

     #endregion 

     #region the Extension's 

     /// <summary> 
     /// Verifies the name of the property for the specified instance. 
     /// </summary> 
     /// <param name="bindableObject">The bindable object.</param> 
     /// <param name="propertyName">Name of the property.</param> 
     [Conditional("DEBUG")] 
     public static void VerifyPropertyName(this INotifyPropertyChanged bindableObject, string propertyName) 
     { 
      bool propertyExists = TypeDescriptor.GetProperties(bindableObject).Find(propertyName, false) != null; 
      if (!propertyExists) 
       throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, 
        "{0} is not a public property of {1}", propertyName, bindableObject.GetType().FullName)); 
     } 

     /// <summary> 
     /// Gets the property name from expression. 
     /// </summary> 
     /// <param name="notifyObject">The notify object.</param> 
     /// <param name="propertyExpression">The property expression.</param> 
     /// <returns>a string containing the name of the property.</returns> 
     public static string GetPropertyNameFromExpression<T>(this INotifyPropertyChanged notifyObject, Expression<Func<T>> propertyExpression) 
     { 
      return GetPropertyNameFromExpression(propertyExpression); 
     } 

     /// <summary> 
     /// Raises a property changed event. 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="bindableObject">The bindable object.</param> 
     /// <param name="propertyExpression">The property expression.</param> 
     public static void RaisePropertyChanged<T>(this INotifyPropertyChanged bindableObject, Expression<Func<T>> propertyExpression) 
     { 
      RaisePropertyChanged(bindableObject, GetPropertyNameFromExpression(propertyExpression)); 
     } 

     #endregion 

     /// <summary> 
     /// Raises the property changed on the specified bindable Object. 
     /// </summary> 
     /// <param name="bindableObject">The bindable object.</param> 
     /// <param name="propertyName">Name of the property.</param> 
     private static void RaisePropertyChanged(INotifyPropertyChanged bindableObject, string propertyName) 
     { 
      bindableObject.VerifyPropertyName(propertyName); 
      RaiseInternalPropertyChangedEvent(bindableObject, GetPropertyChangedEventArgs(propertyName)); 
     } 

     /// <summary> 
     /// Raises the internal property changed event. 
     /// </summary> 
     /// <param name="bindableObject">The bindable object.</param> 
     /// <param name="eventArgs">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param> 
     private static void RaiseInternalPropertyChangedEvent(INotifyPropertyChanged bindableObject, PropertyChangedEventArgs eventArgs) 
     { 
      // get the internal eventDelegate 
      var bindableObjectType = bindableObject.GetType(); 

      // search the base type, which contains the PropertyChanged event field. 
      FieldInfo propChangedFieldInfo = null; 
      while (bindableObjectType != null) 
      { 
       propChangedFieldInfo = bindableObjectType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic); 
       if (propChangedFieldInfo != null) 
        break; 

       bindableObjectType = bindableObjectType.BaseType; 
      } 
      if (propChangedFieldInfo == null) 
       return; 

      // get prop changed event field value 
      var fieldValue = propChangedFieldInfo.GetValue(bindableObject); 
      if (fieldValue == null) 
       return; 

      MulticastDelegate eventDelegate = fieldValue as MulticastDelegate; 
      if (eventDelegate == null) 
       return; 

      // get invocation list 
      Delegate[] delegates = eventDelegate.GetInvocationList(); 

      // invoke each delegate 
      foreach (Delegate propertyChangedDelegate in delegates) 
       propertyChangedDelegate.Method.Invoke(propertyChangedDelegate.Target, new object[] { bindableObject, eventArgs }); 
     } 

     /// <summary> 
     /// Gets the property name from an expression. 
     /// </summary> 
     /// <param name="propertyExpression">The property expression.</param> 
     /// <returns>The property name as string.</returns> 
     private static string GetPropertyNameFromExpression<T>(Expression<Func<T>> propertyExpression) 
     { 
      var lambda = (LambdaExpression)propertyExpression; 

      MemberExpression memberExpression; 

      if (lambda.Body is UnaryExpression) 
      { 
       var unaryExpression = (UnaryExpression)lambda.Body; 
       memberExpression = (MemberExpression)unaryExpression.Operand; 
      } 
      else memberExpression = (MemberExpression)lambda.Body; 

      return memberExpression.Member.Name; 
     } 

     /// <summary> 
     /// Returns an instance of PropertyChangedEventArgs for the specified property name. 
     /// </summary> 
     /// <param name="propertyName"> 
     /// The name of the property to create event args for. 
     /// </param> 
     private static PropertyChangedEventArgs GetPropertyChangedEventArgs(string propertyName) 
     { 
      PropertyChangedEventArgs args; 

      lock (NotifyPropertyChangeExtension.syncLock) 
      { 
       if (!eventArgCache.TryGetValue(propertyName, out args)) 
        eventArgCache.Add(propertyName, args = new PropertyChangedEventArgs(propertyName)); 
      } 

      return args; 
     } 
    } 
} 

मैं, मूल कोड के कुछ हिस्सों से हटाया तो विस्तार के रूप में है काम करना चाहिए, मेरी लाइब्रेरी के अन्य भागों के लिए संदर्भ के बिना। लेकिन यह वास्तव में परीक्षण नहीं किया गया है।

पीएस कोड के कुछ हिस्सों को किसी और से उधार लिया गया था। मुझ पर शर्म आती है, कि मैं इसे कहां से भूल गया। :(

+0

का उपयोग नहीं करते हैं बहुत बहुत धन्यवाद। असल में, आपका उत्तर यहां एकमात्र ऐसा है जो विरासत वृक्ष की जांच करता है और उचित नल-जांच करता है। – firegurafiku

+0

आप ईवेंट तर्क को कैश क्यों करते हैं? आवश्यक सामान, शब्दकोश और ताला, किसी भी संभावित प्रदर्शन लाभ से अधिक है, imho। क्या मुझे कुछ याद आ रहा है (साझा राज्य, आदि)? – skataben

3

ऐसा लगता है कि Wiebe Cnossen द्वारा accepted answer से कोड इस एक लाइनर को सरल बनाया जा सकता है?

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source)) 
    .DynamicInvoke(source, eventArgs); 
संबंधित मुद्दे