1
votes

I'm working on a Windows Phone app using Silverlight C# and XAML. My page contains a ListBox which renders a list of databound objects that the user can manipulate, i.e. add/rename/delete.

I've got it working that the add/rename of items is done in-place, i.e. by swapping a TextBlock for a TextBox depending on the state of the object (bool IsEditable property) and making use of a parameterized VisibilityConverter to manage the opposite Visibility states.

<UserControl.Resources>
    <local:VisibilityConverter x:Key="VisibilityConverter" True="Visible" False="Collapsed"/>
    <local:VisibilityConverter x:Key="InvertedVisibility" True="Collapsed" False="Visible"/>
</UserControl.Resources>
...
<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}" />
<TextBox Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}"/>

The thing is that I also want the TextBox to automatically grab focus when it becomes visible, so that the on-screen keyboard pops up without the user having to tap the TextBox.

Since there's no VisibilityChanged event on a regular TextBox, I subclassed TextBox to TextBox2 and added my own:

public class TextBox2 : TextBox
{
    public TextBox2()
    {
        DefaultStyleKey = typeof(TextBox);
    }

    public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
        "VisibilityChanged",
        typeof(string),
        typeof(TextBox2),
        new PropertyMetadata("Set the VisibilityChanged event handler"));

    public event VisibilityChangedEventHandler VisibilityChanged;

    public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);

    public new Visibility Visibility
    {
        get
        {
            return base.Visibility;
        }
        set
        {
            if (base.Visibility != value)
            {
                base.Visibility = value;
                VisibilityChanged(this, new EventArgs());
            }
        }
    }
}

Now my XAML looks like this:

<TextBlock Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource InvertedVisibility}}"/>
<local:TextBox2 Text="{Binding Name}" Visibility="{Binding IsEditable, Converter={StaticResource VisibilityConverter}}" VisibilityChanged="ListEdit_VisibilityChanged"/>

And the event handler like this:

void ListEdit_VisibilityChanged(object sender, EventArgs e)
{
    TextBox textBox = (TextBox)sender;
    if (textBox.Visibility == System.Windows.Visibility.Collapsed)
        return;
    textBox.Focus();
}

The TextBox2 renders properly and behaves just like a TextBox at runtime, but my VisibilityChanged event handler is not firing when the databinding flips the value of IsEditable.

IsEditable defines the Visibility and the TextBox2 does become visible correctly, so the databinding is working.

I can cause the event to fire programmatically by getting hold of the TextBox2 instance and setting the Visibility of that in code. That also works.

But this databinding scenario being responsible for setting the Visibility seems not to work.

Any ideas why not?

2

2 Answers

2
votes

Here are 2 solutions that I use.

Solution 1 needs no sub class, but solution 2 is more reusable.

1. You can subscribe to the Loaded event of the TextBox, and force a focus, like so:

    void TextBox_Loaded_Focus(object sender, System.Windows.RoutedEventArgs e) {
        ForceFocusControl((Control)sender);            
    }

    void ForceFocusControl(Control control) {

        control.Focus();

        if (FocusManager.GetFocusedElement() != control) {

            Dispatcher.BeginInvoke(() => ForceFocusControl(control));
        }
    }

This solution goes into a recursive loop though, you might want to add some checks to make it safer.

2. Keep your subclass TextBox2, and rather create a private MyVisibility dependency property that you bind to the Visibility property of the base class, but also specify a DependencyProperty_Changed handler, like so:

    /// <summary>
    /// <see cref="TextBox2"/> will focus itself when it becomes visible.
    /// </summary>
    public sealed class TextBox2 : TextBox {

        public TextBox2() {

            SetBinding(TextBox2.MyVisibilityProperty, new Binding("Visibility") { Source = this });
        }

        static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register(
            /* name = */ "MyVisibilityProperty",
            /* property type = */ typeof(Visibility),
            /* owner type = */ typeof(TextBox2),
            /* meta = */ new PropertyMetadata(MyVisibilityProperty_Changed));

        static void MyVisibilityProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) {

            TextBox2 TextBox2 = (TextBox2)d;

            if (TextBox2.Visibility == Visibility.Visible) {
                TextBox2.Focus();
            }
        }
    }
0
votes

This is how my TextBox2 class looks now:

public class TextBox2 : TextBox
{
    public event VisibilityChangedEventHandler VisibilityChanged;
    public delegate void VisibilityChangedEventHandler(object sender, EventArgs e);
    public static readonly DependencyProperty VisibilityChangedProperty = DependencyProperty.Register(
        "VisibilityChanged", typeof(VisibilityChangedEventHandler), typeof(TextBox2), null);

    static readonly DependencyProperty MirrorVisibilityProperty = DependencyProperty.Register(
        "MirrorVisibility", typeof(Visibility), typeof(TextBox2), new PropertyMetadata(MirrorVisibilityChanged));

    public TextBox2()
    {
        SetBinding(TextBox2.MirrorVisibilityProperty, new Binding("Visibility") { Source = this });
    }

    static void MirrorVisibilityChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        ((TextBox2)obj).VisibilityChanged(obj, null); // raise event
    }
}