2

I'm deriving a WPF TextBox control to create a control that only accepts a US currency value as an input. I'm aware that this has been done before and that there are existing libraries I could use, but this is more of a learning exercise borne from a failed attempt to use one of those existing library controls -- it didn't suit my requirements.

In doing this, I'm trying to prevent the textbox from accepting text that does not fit the US currency format (i.e. optional leading currency symbol, decimal numbers, optional group separators, optional fractional component). I'm aware that there is the PreviewTextInput event. Many sources I googled suggested (with much approbation from the community) that one can simply handle this event and reject unwanted input by setting e.Handled = true (setting aside for the moment that this won't work for copy/pasted text, an updated data binding, or a design-time XAML value, to name a few).

I have been wondering whether this approach works all the time. Given that the order that event handlers are called is not guaranteed, how do I know that my control's event handler is called first? Put another way: how do I know that someone's event handler doesn't run first and do something else with the value that allows the format I'm trying to disallow and then sets the e.Handled = true? What about the OnPreviewTextInput method? I believe that suffers from a similar concern, does it not?

Community
  • 1
  • 1
rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • If you create you custom TextBox, and then use it and then create PreviewTextInput, this will run before the one that is inside the TextBox you are extending. I bielieve that preview key down runs before PreviewTextInput, you could handle it there. – adminSoftDK Nov 10 '16 at 15:14

1 Answers1

0

It is indeed very good question.As you pointed it is ordered by how you registered the eventhandlers.If you manipulate via reflection at runtime and change the handlers order,it could work as it is expected.I prepared a scenerio just as you told above.

here I defined an attribute

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CustomAttribute : Attribute
{
    private int _value;

    public int Value
    {
        get { return _value; }
        set { _value = value; }
    }

    private string _eventName;

    public string EventName
    {
        get { return _eventName; }
        set { _eventName = value; }
    }


    public CustomAttribute()
    {

    }
}

andI created a custom box extended from TextBox

public class CustomBox : TextBox
{
    public CustomBox()
    {
        this.PreviewTextInput += CustomBox_TextChanged;
        this.PreviewTextInput += CustomBox_PreviewTextInput;
    }

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        foreach (var item in typeof(CustomBox).GetRuntimeMethods().ToList())
        {
            var a = item.GetCustomAttributes();

            // unsubscribe 
            foreach (var i in a)
            {
                if (i.GetType() == typeof(CustomAttribute))
                {
                    if (((CustomAttribute)i).Value > 0)
                    {
                        RemoveEvent(((CustomAttribute)i).EventName, item.Name);
                    }
                }
            }
        }
        // subscribe according to your order 
        var methods = typeof(CustomBox).GetRuntimeMethods()
                  .Where(m => m.GetCustomAttributes(typeof(CustomAttribute), false).Length > 0)
                  .ToList();

        foreach (var item in methods.OrderBy(m => ((CustomAttribute)m.GetCustomAttribute(typeof(CustomAttribute))).Value))
        {
            AddEvent(((CustomAttribute)item.GetCustomAttribute(typeof(CustomAttribute))).EventName, item.Name);
        }

    }
    private void RemoveEvent(string eventName, string methodName)
    {
        EventInfo ev = this.GetType().GetEvent(eventName);
        Type tDelegate = ev.EventHandlerType;
        MethodInfo miHandler = typeof(CustomBox).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
        Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
        ev.RemoveEventHandler(this, d);
    }

    private void AddEvent(string eventName,string methodName)
    {
        EventInfo ev = this.GetType().GetEvent(eventName);
        Type tDelegate = ev.EventHandlerType;
        MethodInfo miHandler = typeof(CustomBox).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
        Delegate d = Delegate.CreateDelegate(tDelegate, this, miHandler);
        ev.AddEventHandler(this,d);
    }

    [CustomAttribute(EventName = "PreviewTextInput",Value = 2)]
    private void CustomBox_TextChanged(object sender, TextCompositionEventArgs e)
    {
        this.Text = e.Text;
        e.Handled = true;
    }

    [CustomAttribute(EventName = "PreviewTextInput", Value = 1)]
    private void CustomBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        if (e.Text.Contains("e"))
        {
            e.Handled = true;
        }
        else e.Handled = false;
    }
}

Above, even if someone creates

this.PreviewTextInput += CustomBox_TextChanged; handler that manipulates textbox text and change it to unwilling text and blocks another event by e.handle = true;

just before this.PreviewTextInput += CustomBox_PreviewTextInput; is created, Reflection changes their orders according to how you define.

FreeMan
  • 1,417
  • 14
  • 20