Search code examples
c#uwp-xamltemplatebinding

UWP Binding the TextBox PlaceholderTextContentPresenter Foreground to a property in the UserControl's viewmdoel


In trying to bind the color of PlaceholderText in a TextBox, I have elected to try placing the Template into a style I can modify:

<Style x:Key="TextBoxStyle" TargetType="TextBox">
    <Setter Property="Template">
...
<ContentPresenter x:Name="PlaceholderTextContentPresenter" Foreground="{Binding HintTextColor}" />

My UserControl:

<UserControl x:Name="This"
    <TextBox
        Style="{StaticResource TextBoxStyle}"
        x:Name="_textBox"
    />
/>

I've tried variations on:

Foreground="{Binding DataContext.HintTextColor, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}}" 

Also tried (no binding errors)

<ContentPresenter x:Name="PlaceholderTextContentPresenter" Foreground="{Binding ElementName=This.DataContext.HintTextColor}" />

Trying to connect the Foreground to the correct DataContext and color property, but either the syntax is supported only in WPF or I get a binding error in output. How do I bind the Foreground to the control view model HintTextColor?

Edit

The final solution looks like this for me:

<UserControl
    <customControlsUwp:CustomTextBox
        PlaceholderForeground="{Binding HintTextColor, Converter={StaticResource StringColorToBrushConverter}}"
    />
/>

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

    private ContentPresenter _placeholderTextContentPresenter;

    public SolidColorBrush PlaceholderForeground {
        get => (SolidColorBrush)GetValue(PlaceholderForegroundProperty);
        set => SetValue(PlaceholderForegroundProperty, value);
    }

    //Add DP PlaceholderForeground for runtime bound changes to HintTextColor
    public static readonly DependencyProperty PlaceholderForegroundProperty =
        DependencyProperty.Register("PlaceholderForeground", typeof(SolidColorBrush), typeof(CustomTextBox), new PropertyMetadata(null, PropertyChangedCallback));


    public static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {

        if (!(d is CustomTextBox customTextBox) || !(e.NewValue is SolidColorBrush solidColorBrush))
            return;

        if (customTextBox._placeholderTextContentPresenter == null) 
            customTextBox._placeholderTextContentPresenter.Foreground = solidColorBrush;
    }

    protected override void OnApplyTemplate() { 
        base.OnApplyTemplate();
        _placeholderTextContentPresenter = GetTemplateChild("PlaceholderTextContentPresenter") as ContentPresenter;
    }
}

And added a template binding in the control style to capture the first value set:

<ContentPresenter x:Name="PlaceholderTextContentPresenter" Foreground="{TemplateBinding PlaceholderForeground}" Grid.ColumnSpan="2" Content="{TemplateBinding PlaceholderText}" IsHitTestVisible="False" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" TextWrapping="{TemplateBinding TextWrapping}"/>

Solution

  • UWP doesn't support Binding in style. You could see the Migration notes section in MSDN document.

    You could make a custom TextBox and define a dependency property named as PlaceholderForeground. In its override OnApplyTemplate method, you could get the "PlaceholderTextContentPresenter" ContentPresenter control from its default style's template. Then, when your dependency property is changed, you could set the new value to the "PlaceholderTextContentPresenter".Foreground.

    public class CustomTextBox : TextBox
    {
        private ContentPresenter _PlaceholderTextContentPresenter;
    
        public SolidColorBrush PlaceholderForeground
        {
            get { return (SolidColorBrush)GetValue(PlaceholderForegroundProperty); }
            set { SetValue(PlaceholderForegroundProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PlaceholderForegroundProperty =
            DependencyProperty.Register("PlaceholderForeground", typeof(SolidColorBrush), typeof(CustomTextBox), new PropertyMetadata(null, PropertyChangedCallback));
    
    
        public static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if ((d as CustomTextBox)._PlaceholderTextContentPresenter !=null)
            {
                (d as CustomTextBox)._PlaceholderTextContentPresenter.Foreground = e.NewValue as SolidColorBrush;
            }
        }
    
        protected override void OnApplyTemplate()
        {
            _PlaceholderTextContentPresenter = GetTemplateChild("PlaceholderTextContentPresenter") as ContentPresenter;
        }
    
        public CustomTextBox()
        {
            DefaultStyleKey = typeof(CustomTextBox);
    
        }
    }
    
    <Application
    x:Class="AppUsercontrol.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AppUsercontrol"
    RequestedTheme="Light">
    <Application.Resources>
        <Style x:Key="CustomTextBoxStyle1" TargetType="local:CustomTextBox">
            <Setter Property="MinWidth" Value="{ThemeResource TextControlThemeMinWidth}"/>
            <Setter Property="MinHeight" Value="{ThemeResource TextControlThemeMinHeight}"/>
            <Setter Property="Foreground" Value="{ThemeResource TextControlForeground}"/>
            <Setter Property="Background" Value="{ThemeResource TextControlBackground}"/>
            <Setter Property="BorderBrush" Value="{ThemeResource TextControlBorderBrush}"/>
            <Setter Property="SelectionHighlightColor" Value="{ThemeResource TextControlSelectionHighlightColor}"/>
            <Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}"/>
            <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}"/>
            <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}"/>
            <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Auto"/>
            <Setter Property="ScrollViewer.VerticalScrollMode" Value="Auto"/>
            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
            <Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/>
            <Setter Property="Padding" Value="{ThemeResource TextControlThemePadding}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:CustomTextBox">
                        <Grid>
                            <Grid.Resources>
                                <Style x:Name="DeleteButtonStyle" TargetType="Button">
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="Button">
                                                <Grid x:Name="ButtonLayoutGrid" BorderBrush="{ThemeResource TextControlButtonBorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{ThemeResource TextControlButtonBackground}">
                                                    <VisualStateManager.VisualStateGroups>
                                                        <VisualStateGroup x:Name="CommonStates">
                                                            <VisualState x:Name="Normal"/>
                                                            <VisualState x:Name="PointerOver">
                                                                <Storyboard>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonLayoutGrid">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBackgroundPointerOver}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonLayoutGrid">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBorderBrushPointerOver}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="GlyphElement">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonForegroundPointerOver}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                </Storyboard>
                                                            </VisualState>
                                                            <VisualState x:Name="Pressed">
                                                                <Storyboard>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="ButtonLayoutGrid">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBackgroundPressed}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="ButtonLayoutGrid">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonBorderBrushPressed}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="GlyphElement">
                                                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlButtonForegroundPressed}"/>
                                                                    </ObjectAnimationUsingKeyFrames>
                                                                </Storyboard>
                                                            </VisualState>
                                                            <VisualState x:Name="Disabled">
                                                                <Storyboard>
                                                                    <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ButtonLayoutGrid"/>
                                                                </Storyboard>
                                                            </VisualState>
                                                        </VisualStateGroup>
                                                    </VisualStateManager.VisualStateGroups>
                                                    <TextBlock x:Name="GlyphElement" AutomationProperties.AccessibilityView="Raw" Foreground="{ThemeResource TextControlButtonForeground}" FontStyle="Normal" FontSize="12" FontFamily="{ThemeResource SymbolThemeFontFamily}" HorizontalAlignment="Center" Text="&#xE10A;" VerticalAlignment="Center"/>
                                                </Grid>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </Grid.Resources>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="HeaderContentPresenter">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlHeaderForegroundDisabled}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundDisabled}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushDisabled}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundDisabled}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlPlaceholderForegroundDisabled}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="PointerOver">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushPointerOver}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundPointerOver}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlPlaceholderForegroundPointerOver}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundPointerOver}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Focused">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="PlaceholderTextContentPresenter">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlPlaceholderForegroundFocused}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBackgroundFocused}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlBorderBrushFocused}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextControlForegroundFocused}"/>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="RequestedTheme" Storyboard.TargetName="ContentElement">
                                                <DiscreteObjectKeyFrame KeyTime="0" Value="Light"/>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="ButtonStates">
                                    <VisualState x:Name="ButtonVisible">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="DeleteButton">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Visible</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="ButtonCollapsed"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border x:Name="BorderElement" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.ColumnSpan="2" Grid.Row="1" Grid.RowSpan="1"/>
                            <ContentPresenter x:Name="HeaderContentPresenter" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" Foreground="{ThemeResource TextControlHeaderForeground}" FontWeight="Normal" Margin="0,0,0,8" Grid.Row="0" TextWrapping="{TemplateBinding TextWrapping}" Visibility="Collapsed" x:DeferLoadStrategy="Lazy"/>
                            <ScrollViewer x:Name="ContentElement" AutomationProperties.AccessibilityView="Raw" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled"/>
                            <ContentPresenter x:Name="PlaceholderTextContentPresenter" Grid.ColumnSpan="2" Content="{TemplateBinding PlaceholderText}" IsHitTestVisible="False" Margin="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" Grid.Row="1" TextWrapping="{TemplateBinding TextWrapping}"/>
                            <Button x:Name="DeleteButton" AutomationProperties.AccessibilityView="Raw" BorderThickness="{TemplateBinding BorderThickness}" Grid.Column="1" FontSize="{TemplateBinding FontSize}" IsTabStop="False" Margin="{ThemeResource HelperButtonThemePadding}" MinWidth="34" Grid.Row="1" Style="{StaticResource DeleteButtonStyle}" Visibility="Collapsed" VerticalAlignment="Stretch"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Application.Resources>