Tomtom
Tomtom

Reputation: 9384

Tab-Focus on custom TextBox

In my application I have a TabControl. On one TabItem there are three TextBoxes where I can switch between them by pressing the Tab-Key.

Now I want to replace this standard-TextBoxes with custom-TextBoxes which should have a Null-Text that will be displayed if the Text is empty.

The XAML of my custom-TextBox is:

<UserControl x:Class="MyApplication.Controls.NullTextTextBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:converter="clr-namespace:ScM.Converter"
             mc:Ignorable="d" d:DesignHeight="24" d:DesignWidth="300"
             x:Name="nullTextTextBox" IsHitTestVisible="True" Focusable="True">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBox Grid.Column="0" VerticalAlignment="Stretch" x:Name="tbInput" 
                 Text="{Binding ElementName=nullTextTextBox,Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                 AcceptsReturn="{Binding ElementName=nullTextTextBox, Path=AcceptsReturn, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
                 TextWrapping="{Binding ElementName=nullTextTextBox, Path=TextWrapping, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
                 IsTabStop="True" />
        <TextBlock Grid.Column="0" VerticalAlignment="Top" Text="{Binding ElementName=nullTextTextBox,Path=NullText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"
                   FontStyle="Italic" Foreground="DarkGray" Margin="4,4,0,0" IsHitTestVisible="False"
                   Visibility="{Binding ElementName=nullTextTextBox, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={converter:StringIsNullToVisibilityConverter}}"
                   Focusable="False"/>
        <TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock.Visibility>
                <MultiBinding Converter="{converter:DeleteButtonMultiConverter}">
                    <Binding ElementName="nullTextTextBox" Path="IsClearButtonVisible" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
                    <Binding ElementName="nullTextTextBox" Path="Text" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
                </MultiBinding>
            </TextBlock.Visibility>
            <Hyperlink TextDecorations="{x:Null}" Command="{Binding ElementName=nullTextTextBox, Path=ClearTextCommand, Mode=OneWay}"
                       Focusable="False" >
                <TextBlock FontFamily="Wingdings 2" Text="Î" Foreground="Red" FontWeight="Bold" FontSize="14" VerticalAlignment="Center" Margin="1,1,2,1"/>
            </Hyperlink>
        </TextBlock>
    </Grid>
</UserControl>

I would say the code-behind of this xaml isn't relevant, because there are only DependencyProperties registered.

My default-TextBox behaves like I expect it. But if I press the Tab-Key while the focus is within one NullTextBox the focus is switched to the TabHeader and not to the second NullTextBox.

The xaml where the NullTextBoxes are located it looks like:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <controls:NullTextTextBox Grid.Row="0" NullText="Value 1"/>
    <controls:NullTextTextBox Grid.Row="1" NullText="Value 2"/>
    <controls:NullTextTextBox Grid.Row="2" NullText="Value 3"/>
</Grid>

Why does my second and third NullTextBox not get focused when I press the Tab-Key?


I've found out that if I remove the TextBlock which contains the Hyperlink the Tab-Order works as expected. But I need this TextBlock...


The code-behind of my custom textbox looks like:

public partial class NullTextTextBox : UserControl, INotifyPropertyChanged
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text", typeof (string), typeof (NullTextTextBox), new PropertyMetadata(default(string)));

    public static readonly DependencyProperty NullTextProperty = DependencyProperty.Register(
        "NullText", typeof (string), typeof (NullTextTextBox), new PropertyMetadata(default(string)));

    public static readonly DependencyProperty IsClearButtonVisibleProperty = DependencyProperty.Register(
        "IsClearButtonVisible", typeof (bool), typeof (NullTextTextBox), new PropertyMetadata(default(bool)));

    public static readonly DependencyProperty AcceptsReturnProperty = DependencyProperty.Register(
        "AcceptsReturn", typeof (bool), typeof (NullTextTextBox), new PropertyMetadata(default(bool)));

    public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
        "TextWrapping", typeof (TextWrapping), typeof (NullTextTextBox), new PropertyMetadata(default(TextWrapping)));

    public TextWrapping TextWrapping
    {
        get { return (TextWrapping) GetValue(TextWrappingProperty); }
        set
        {
            SetValue(TextWrappingProperty, value); 
            OnPropertyChanged();
        }
    }

    private ICommand clearTextCommand;

    public NullTextTextBox()
    {
        InitializeComponent();
        IsClearButtonVisible = false;
        Text = string.Empty;
        NullText = "Enter text here...";
        AcceptsReturn = false;
        TextWrapping = TextWrapping.NoWrap;
    }

    public bool AcceptsReturn
    {
        get { return (bool) GetValue(AcceptsReturnProperty); }
        set
        {
            SetValue(AcceptsReturnProperty, value);
            OnPropertyChanged();
        }
    }

    public ICommand ClearTextCommand
    {
        get { return clearTextCommand ?? (clearTextCommand = new RelayCommand<object>(p => ClearText())); }
    }

    public bool IsClearButtonVisible
    {
        get { return (bool) GetValue(IsClearButtonVisibleProperty); }
        set
        {
            SetValue(IsClearButtonVisibleProperty, value);
            OnPropertyChanged();
        }
    }

    public string Text
    {
        get { return (string) GetValue(TextProperty); }
        set
        {
            SetValue(TextProperty, value);
            OnPropertyChanged();
        }
    }

    public string NullText
    {
        get { return (string) GetValue(NullTextProperty); }
        set
        {
            SetValue(NullTextProperty, value);
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void ClearText()
    {
        Text = string.Empty;
        tbInput.Focus();
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

and the converter used:

internal class DeleteButtonMultiConverter : MarkupExtension, IMultiValueConverter
{
    private static DeleteButtonMultiConverter converter;

    public DeleteButtonMultiConverter()
    {

    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values != null && values.Length == 2 && values[0] is bool && values[1] is string)
        {
            if ((bool) values[0] && !string.IsNullOrEmpty((string) values[1]))
                return Visibility.Visible;
            return Visibility.Collapsed;
        }
        return values;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return converter ?? (converter = new DeleteButtonMultiConverter());
    }
}

Upvotes: 1

Views: 332

Answers (1)

almulo
almulo

Reputation: 4978

Change the TextBlock inside your Hyperlink for a Run, like this (note that, since Run doesn't support VerticalAlignment or Margin, I've either removed or moved those properties):

<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1,1,2,1">
    <TextBlock.Visibility>
        <MultiBinding Converter="{converter:DeleteButtonMultiConverter}">
            <Binding ElementName="nullTextTextBox" Path="IsClearButtonVisible" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
            <Binding ElementName="nullTextTextBox" Path="Text" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
        </MultiBinding>
    </TextBlock.Visibility>
    <Hyperlink TextDecorations="{x:Null}" Command="{Binding ElementName=nullTextTextBox, Path=ClearTextCommand, Mode=OneWay}"
               Focusable="False" >
        <Run FontFamily="Wingdings 2" Text="Î" Foreground="Red" FontWeight="Bold" FontSize="14" />
    </Hyperlink>
</TextBlock>

Upvotes: 1

Related Questions