14

I am implementing Key Navigation for an application and I want to override the space key functionality when a Combo Box is focused such that it acts like an enter key; like this:

if (!cb.IsDropDownOpen)
{
  cb.IsDropDownOpen = true;
}
else
{
  cb.SelectedItem = cb.{non-public member HighlightedItem};
  cb.IsDropDownOpen = false;
}

The problem is that I need to get the value of that non-public member so that I can set the selected value and close the drop-down (how enter would normally work).

Now the question is: What is the fastest and hassle free way of achieving this?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
rotSin
  • 231
  • 1
  • 3
  • 13
  • No need to mark your title "solved", that's what accepted answer feature is for. – Joel Coehoorn Feb 17 '11 at 17:00
  • Hey good to know, i'll fix that right up in a previous post of mine. Thanks for telling me, I was a little unsure about the whole _solved_ thing :) – rotSin Feb 17 '11 at 17:04

2 Answers2

24

This is a helper class I have for doing just this:

public static class PropertyHelper
{
    /// <summary>
    /// Returns a _private_ Property Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Property</typeparam>
    /// <param name="obj">Object from where the Property Value is returned</param>
    /// <param name="propName">Propertyname as string.</param>
    /// <returns>PropertyValue</returns>
    public static T GetPrivatePropertyValue<T>(this object obj, string propName)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        PropertyInfo pi = obj.GetType().GetProperty(propName,
                                                    BindingFlags.Public | BindingFlags.NonPublic |
                                                    BindingFlags.Instance);
        if (pi == null)
            throw new ArgumentOutOfRangeException("propName",
                                                  string.Format("Property {0} was not found in Type {1}", propName,
                                                                obj.GetType().FullName));
        return (T) pi.GetValue(obj, null);
    }

    /// <summary>
    /// Returns a private Field Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Field</typeparam>
    /// <param name="obj">Object from where the Field Value is returned</param>
    /// <param name="propName">Field Name as string.</param>
    /// <returns>FieldValue</returns>
    public static T GetPrivateFieldValue<T>(this object obj, string propName)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        Type t = obj.GetType();
        FieldInfo fi = null;
        while (fi == null && t != null)
        {
            fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            t = t.BaseType;
        }
        if (fi == null)
            throw new ArgumentOutOfRangeException("propName",
                                                  string.Format("Field {0} was not found in Type {1}", propName,
                                                                obj.GetType().FullName));
        return (T) fi.GetValue(obj);
    }

    /// <summary>
    /// Sets a _private_ Property Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Property</typeparam>
    /// <param name="obj">Object from where the Property Value is set</param>
    /// <param name="propName">Propertyname as string.</param>
    /// <param name="val">Value to set.</param>
    /// <returns>PropertyValue</returns>
    public static void SetPrivatePropertyValue<T>(this object obj, string propName, T val)
    {
        Type t = obj.GetType();
        if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
            throw new ArgumentOutOfRangeException("propName",
                                                  string.Format("Property {0} was not found in Type {1}", propName,
                                                                obj.GetType().FullName));
        t.InvokeMember(propName,
                       BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty |
                       BindingFlags.Instance, null, obj, new object[] {val});
    }


    /// <summary>
    /// Set a private Field Value on a given Object. Uses Reflection.
    /// </summary>
    /// <typeparam name="T">Type of the Field</typeparam>
    /// <param name="obj">Object from where the Property Value is returned</param>
    /// <param name="propName">Field name as string.</param>
    /// <param name="val">the value to set</param>
    /// <exception cref="ArgumentOutOfRangeException">if the Property is not found</exception>
    public static void SetPrivateFieldValue<T>(this object obj, string propName, T val)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        Type t = obj.GetType();
        FieldInfo fi = null;
        while (fi == null && t != null)
        {
            fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            t = t.BaseType;
        }
        if (fi == null)
            throw new ArgumentOutOfRangeException("propName",
                                                  string.Format("Field {0} was not found in Type {1}", propName,
                                                                obj.GetType().FullName));
        fi.SetValue(obj, val);
    }
}
steinberg
  • 610
  • 4
  • 11
  • Wow, I just saw this; This is some really good stuff for a larger app. I'd want to give answer credits to both of you and vote up but don't have enough rep. Anyway thank you both a lot for such good and speedy answers. **Cheers!** – rotSin Feb 17 '11 at 17:00
  • That is indeed a neat helper class, @steinberg +1 – Adam Rackis Feb 17 '11 at 17:12
  • These methods are more efficient than the selected answer. – Will Du Feb 18 '11 at 04:01
  • Yes I know, but what I requested was the fastest and most hassle free method. I required that in one place only and the selected answer's code did the trick for me in the end. – rotSin Feb 18 '11 at 14:07
18

You'd have to use reflection to get the value of the property

PropertyInfo highlightedItemProperty = cb.GetType().GetProperties(BindingFlags.NonPublic  | BindingFlags.Instance).Single(pi => pi.Name == "HighlightedItem");
object highlightedItemValue = highlightedItemProperty.GetValue(cb, null);

To browse all properties or fields, also check out

var allProps = cb.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).OrderBy(pi => pi.Name).ToList();
var allFields = cb.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).OrderBy(pi => pi.Name).ToList();

(you can read through them all in the debugger)

Adam Rackis
  • 82,527
  • 56
  • 270
  • 393
  • Hello, wow this is really good stuff, I've been checking out the allFields and apparently there is a System.WeakReference _highlightedElement inside. Gonna try with that and reply ASAP. – rotSin Feb 17 '11 at 16:22
  • Your first post confused me a little and I went searching in the fields to no avail. Coming back to Properties I saw there actually was a Highlighted item in the properties and it all worked like a charm: `((ComboBox)sender).SelectedIndex = ((ComboBox)sender).Items.IndexOf((ComboBoxItem)highlightedItemValue);` – rotSin Feb 17 '11 at 16:57
  • Oh, I almost forgot; Hope I'm not spamming but, **Thank You!** – rotSin Feb 17 '11 at 17:08
  • You're very welcome! Glad it worked. Sorry to confuse you before - I said there was no HighlightedItem because I was looking in the WinForms Combobox, not the WPF one. – Adam Rackis Feb 17 '11 at 17:12
  • Thanks Adam. That helped me also! – Nivid Dholakia Feb 15 '13 at 22:35