xamlsilverlightkeyboardsilverlight-5.0silverlight-toolkit

SDK AutoCompleteBox Ignores IsTabStop odd issue


I've got a few sdk AutoCompleteBox's that I just want to set IsTabStop="False" on. If you do so it's ignored and still takes focus/tab action.

So I went digging into the template and found the embedded TextBox in there with it explicitly hardcoded to IsTabStop=True so I figured I could just pull that out, TemplateBind to an unused property like Tag to set it at the instance level and just provide a setter in the template for default True, right?

No joy though, it still ignores it. So I tried just setting it False explicitly in the template. Which sure enough removes it from the Tab order. HOWEVER, it also disables the control so it won't accept focus or edit at all.

I've ran into similar situations in the past but none of my tricks are working. Anyone ran into this before? What am I missing to just make the damn thing fall out of tab order? I'm also rather confused why setting the IsTabStop on the embedded TextBox would disable the control from all hittestvisibility....

Not sure where the reasoning is for setting to just have the property ignored nor have found explanation in any docs yet.

So, maybe another pair of eyes might help before I go too far down the rabbit hole for something seemingly so simple, any thoughts? Thanks!


Solution

  • The TextBox inside the AutoCompleteBox can only own keyboard focus when all criteria for focus ownership are met:

    Therefore you have to let the internal TextBox have its tabstoppy ways. But we have other options. I thought about using a ContentControl and setting TabNavigation="Once". As far as I understood it is supposed to have the Control and all its content behave like one solid piece in the tab navigation chain, so theoretically when tabbing you will step at the ContentControl, but never down into its content.

    But I tried it and it is not working as expected. Don't know where my thinking is wrong here.

    I played around with it and found the following solution is working:

    Surround them with a FocusDitcher (we derive from ContentControl und make the control ditch the focus OnGotFocus, but not if triggered by the user clicking into the inner TextBox). We could alternatively write a Behavior for that. For focus ditching, the control has to sabotage one of the prerequisites for focus ownership, like going to disabled mode and back to enabled (not working with IsTabStop, tried it, failed).

    // helper control
    public class FocusCatcher : Control { }
    
    [TemplatePart( Name = "ReversedTabSequenceEntryPointElement", Type = typeof( UIElement ) )]
    public class FocusDitcher : ContentControl
    {
        public FocusDitcher()
        {
            DefaultStyleKey = typeof( FocusDitcher );
            TabNavigation = KeyboardNavigationMode.Once;
        }
    
        private UIElement m_reversedTabSequenceEntryPoint;
    
        protected override void OnGotFocus( RoutedEventArgs e )
        {
            if (FocusManager.GetFocusedElement() == this)
            {
                DitchFocus();
            }
        }
    
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            m_reversedTabSequenceEntryPoint = (UIElement) GetTemplateChild( "ReversedTabSequenceEntryPointElement" );
            m_reversedTabSequenceEntryPoint.GotFocus += OnReversedTabSequenceEntryPointGotFocus;
        }
    
        private void OnReversedTabSequenceEntryPointGotFocus( object sender, RoutedEventArgs e )
        {
            // tweak tab index to ensure that when we ditch the focus it will go to the first one in the tab order
            // otherwise it would not be possible to ever shift+tab back to any element precceeding this one in the tab order
            var localTabIndexValue = ReadLocalValue(TabIndexProperty);
            TabIndex = Int32.MinValue;
            DitchFocus();
    
            if (DependencyProperty.UnsetValue == localTabIndexValue)
                ClearValue( TabIndexProperty );
            else
                TabIndex = (int) localTabIndexValue;
    
        }
    
        private void DitchFocus()
        {
            IsEnabled = false; // now we lose the focus and it should go to the next one in the tab order
            IsEnabled = true;  // set the trap for next time
        }
    }
    

    and template

    <Style TargetType="myApp:FocusDitcher">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="myApp:FocusDitcher">
                    <Grid>
                        <ContentPresenter
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            Cursor="{TemplateBinding Cursor}"
                            Margin="{TemplateBinding Padding}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <myApp:FocusCatcher x:Name="ReversedTabSequenceEntryPointElement"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>