Search code examples
xamlwinui-3winuiwinui-xaml

ReadOnly functionality for TexBox-like controls


The TextBox control (Microsoft.UI.Xaml.Controls.TextBox) exposes an "IsReadonly" property, which seems to differ from !IsEnabled in the following ways:

  • The content of a ReadOnly TextBox can be selected
  • A ReadOnly TextBox is a tab stop by default
  • ReadOnly TextBoxes are initially visually indistinct from Non-ReadOnly TextBoxes

Other textbox-like controls, such as the NumberBox and AutoSuggestBox, have no such property. Is there any way to convince other controls that are visually similar to a TextBox to have ReadOnly behaviour?


Solution

  • AutoSuggestBox and NumberBox have an inner TextBox. You can access it with the CommunityToolkit.WinUI.Extensions's FindDescendant().

    For example, you can create an AttachedProperty like this:

    public static class Extensions
    {
        public static readonly DependencyProperty IsReadOnlyProperty =
            DependencyProperty.RegisterAttached(
                "IsReadOnly",
                typeof(bool),
                typeof(Extensions),
                new PropertyMetadata(default, OnIsReadOnlyPropertyChanged));
    
        public static bool GetIsReadOnly(DependencyObject obj) => (bool)obj.GetValue(IsReadOnlyProperty);
    
        public static void SetIsReadOnly(DependencyObject obj, bool value) => obj.SetValue(IsReadOnlyProperty, value);
    
        private static void OnIsReadOnlyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is not FrameworkElement element)
            {
                return;
            }
    
            element.Loaded -= Element_Loaded;
    
            if (element.IsLoaded is false)
            {
                element.Loaded += Element_Loaded;
                return;
            }
    
            _ = TryUpdateIsReadOnly(element);
        }
    
        private static void Element_Loaded(object sender, RoutedEventArgs e)
        {
            _ = TryUpdateIsReadOnly(sender);
        }
    
        private static bool TryUpdateIsReadOnly(object sender)
        {
            if (sender is not FrameworkElement element ||
                element.FindDescendant<TextBox>() is not TextBox innerTextBox)
            {
                return false;
            }
    
            innerTextBox.IsReadOnly = GetIsReadOnly(element);
            System.Diagnostics.Debug.WriteLine($"{element.GetType()} IsReadOnly: {innerTextBox.IsReadOnly}");
            return true;
        }
    }
    

    Then use it like this:

    <StackPanel Spacing="8">
        <ToggleSwitch
            x:Name="IsReadOnlyToggleSwitch"
            Header="IsReadOnly"
            OffContent="False"
            OnContent="True" />
        <TextBox
            IsReadOnly="{x:Bind IsReadOnlyToggleSwitch.IsOn, Mode=OneWay}"
            Text="TextBox" />
        <AutoSuggestBox
            local:Extensions.IsReadOnly="{x:Bind IsReadOnlyToggleSwitch.IsOn, Mode=OneWay}"
            Text="AutoSuggestBox" />
        <NumberBox
            local:Extensions.IsReadOnly="{x:Bind IsReadOnlyToggleSwitch.IsOn, Mode=OneWay}"
            Value="12345" />
    </StackPanel>