Question

Right. I've got a small program (that replicates my issue). Basically, it tries to bind to some properties of the object it's styling. It kind of works: it gives me the default value (from the dependency property). I've been thinking this may be because the Style's RelativeSource Self isn't the same as the TextBox it's styling's one. But I don't know. I've tried debugging this, checking time and again that the value set in XAML was actually set. The thing is, with a smaller test program it works. This is just a scale up from that. I don't know what's going wrong. Thanks!

The code for reproducing this issue:

MainWindow.xaml

<Window x:Class="MyNamespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lcl="clr-namespace:MyNamespace"
        Title="My title." Height="350" Width="425" MaxHeight="350" MaxWidth="425" MinHeight="350" MinWidth="425">
    <Window.Resources>
        <ResourceDictionary Source="TestDictionary.xaml"/>
    </Window.Resources>
    <Grid>
        <TextBox Style="{StaticResource TextBoxWithDefault}" FontSize="36" lcl:MyOptions.Default="Not default." VerticalAlignment="Center"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace MyNamespace
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
    public static class MyOptions
    {
        public static string GetDefault(DependencyObject obj)
        {
            return (string)obj.GetValue(DefaultProperty);
        }
        public static void SetDefault(DependencyObject obj, string value)
        {
            obj.SetValue(DefaultProperty, value);
        }
        public static readonly DependencyProperty DefaultProperty =
            DependencyProperty.RegisterAttached(
                "Default",
                typeof(string),
                typeof(MyOptions),
                new PropertyMetadata("Default"));
    }
}

TestDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:lcl="clr-namespace:MyNamespace"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    <Style TargetType="TextBox" x:Key="TextBoxWithDefault">
        <Style.Resources>
            <Label Content="{Binding Path=(lcl:MyOptions.Default), Mode=TwoWay, RelativeSource={RelativeSource Self}}"
                                       Foreground="LightGray"
                                       FontSize="{Binding Path=(FontSize), Mode=TwoWay, RelativeSource={RelativeSource Self}}" x:Key="TheLabel"/>
        </Style.Resources>
        <Style.Triggers>
            <Trigger Property="Text" Value="{x:Static sys:String.Empty}">
                <Setter Property="Background">
                    <Setter.Value>
                        <VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
                    </Setter.Value>
                </Setter>
            </Trigger>
            <Trigger Property="Text" Value="{x:Null}">
                <Setter Property="Background">
                    <Setter.Value>
                        <VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None" Visual="{DynamicResource TheLabel}"/>
                    </Setter.Value>
                </Setter>
            </Trigger>
            <Trigger Property="IsKeyboardFocused" Value="True">
                <Setter Property="Background" Value="White"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

I have no idea what's going wrong here, as a scaled down version of this works perfectly. There's probably something I overlooked, that will seem pretty obvious when I find it. But I can't find it now.

EDIT: Well, it seems I was dumb. The original version (here) uses a Trigger, which means that it gets the parent textbox's value. The question now is: how can I get it working?

Thanks for your time!

Was it helpful?

Solution

The real show-stopper here is that when you use the Label in a VisualBrush, the label isn't part of the TextBox' "Visual Tree" (see for example Sheldon Xiao's answer to this similar question on MSDN: Binding Problem inside VisualBrush).

This means that the label won't inherit the text box' DataContext, and you can't reach the text box from a RelativeSource binding either. In contrast, the accepted answer in your other post sets the actual content of a button, which does make the content part of the button's visual tree.

So I don't think there's a pure XAML solution to this problem - pushing the correct MyOptions.Default from the text box to the label. One possible code-based solution is to scrap the TextBoxWithDefault style and do everything from your attached property when Default changes:

...
public static readonly DependencyProperty DefaultProperty =
    DependencyProperty.RegisterAttached(
        "Default",
        typeof(string),
        typeof(MyOptions),

        //Listen for changes in "Default":
        new PropertyMetadata(null, OnMyDefaultChanged));

private static void OnMyDefaultChanged(DependencyObject sender, 
                                       DependencyPropertyChangedEventArgs e)
{
    var text = (TextBox)sender;
    var myDefault = e.NewValue;

    var defaultLabel = new Label();
    defaultLabel.Foreground = Brushes.LightGray;

    //Explicitly bind the needed value from the TextBox:
    defaultLabel.SetBinding(Label.ContentProperty,
                            new Binding()
                            {
                                Source = text,
                                Path = new PropertyPath(MyOptions.DefaultProperty)
                            });

    text.Background = new VisualBrush()
    {
        Visual = defaultLabel,
        AlignmentX = AlignmentX.Left,
        AlignmentY = AlignmentY.Center,
        Stretch = Stretch.None
    };
    text.TextChanged += new TextChangedEventHandler(OnTextWithDefaultChanged);
}

private static void OnTextWithDefaultChanged(object sender, 
                                             TextChangedEventArgs e)
{
    var text = (TextBox)sender;
    var defaultLabel = (text.Background as VisualBrush).Visual as Label;

    defaultLabel.Visibility = string.IsNullOrEmpty(text.Text) ? 
                                                Visibility.Visible : 
                                                Visibility.Collapsed;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top