Frage

I'm trying to write a Button control that displays a background gradient, starting with a color specified by a custom property, and ending with a hard-coded color. Seems to me this should be a straightforward control template, but I can't get it to work.

Subclassed "Button" with a Color dependency property:

public class GradientButton : Button
{
    public Color BackgroundColor
    {
        get { return (Color)GetValue(BackgroundColorProperty); }
        set { SetValue(BackgroundColorProperty, value); }
    }

    public static readonly DependencyProperty BackgroundColorProperty =
        DependencyProperty.Register("BackgroundColor", typeof(Color), typeof(GradientButton), new PropertyMetadata((sender, args) => {
            System.Diagnostics.Debug.WriteLine("Set bg col");
        }));

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

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(GradientButton), null);
    public GradientButton()
        : base()
    {
        this.DefaultStyleKey = typeof(GradientButton);
    }
}

Control template, which sets a background gradient using the BackgroundColor DP:

<Style TargetType="custom:GradientButton">
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Top" />
    <Setter Property="Width" Value="200" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="custom:GradientButton">
                <Grid>
                    <Grid.Background>
                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                            <GradientStop Color="{TemplateBinding BackgroundColor}" Offset="0" />
                            <GradientStop Color="Gray" Offset="1" />
                        </LinearGradientBrush>
                    </Grid.Background>

                    <TextBlock Text="{TemplateBinding Text}" />
                    <TextBlock Text="{TemplateBinding BackgroundColor}" HorizontalAlignment="Right" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

If I use the control like this:

<custom:GradientButton Text="Foo" BackgroundColor="#FF0000" /> 

then I expect to see a button with red-to-gray background gradient. Instead, only the gray part of the gradient appears:

Control does not show the red part of the background

What painfully obvious thing am I missing?


Edit For emphasis:

  1. I added "Text" DP to prove my DP / TemplateBinding is in fact kosher.
  2. Note that I can't display the "BackgroundColor", even as text in a text block -- and yet I have confirmed in the debugger that it is in fact being set on the control.
War es hilfreich?

Lösung

Sorry for making confuse. My answer is not correct. The issue here is TemplateBinding. TemplateBinding is a lightwight binding at compile time while using TemplatedParent happens at runtime, thus TemplateBinding has some limitation, one of which is it cannot bind to Freezables. What you can do is to use traditional templated parent instead:

<GradientStop Color="{Binding Path=BackgroundColor, RelativeSource={RelativeSource TemplatedParent}}" Offset="0" />

You can refer to http://blogs.msdn.com/b/liviuc/archive/2009/12/14/wpf-templatebinding-vs-relativesource-templatedparent.aspx

"You'd think the compiler or the runtime would throw some kind of error, but apparently not."

A good point. I did not see any binding error either in output window. However, I used Snoop to look into Color property and found exactly what I expected:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=BackgroundColor; DataItem=null; target element is 'GradientStop' (HashCode=4605357); target property is 'Color' (type 'Color')

Andere Tipps

No problem,

Your first issue is how you're declaring your Gradient. The way you currently have will show a solid line of Red (or whatever color specified) for the top half, and a solid line of Gray for the bottom half. You want something like this and it does the rest of it for you.

<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
     <GradientStop Color="Red" Offset="0"/>
     <GradientStop Color="Gray" Offset="1"/>
</LinearGradientBrush>

Also, you could make this easier on yourself and just utilize properties already existent that are there for purposes just like this one so you don't need any additional code. Like the Tag Property (which personally I stuff all sorts of different crap into for different purposes depending on the instances. So something like this will also suffice just fine without the need for the extra dependency declarations (unless it really matters that's called "BackgroundColor");

<Style TargetType="custom:GradientButton">
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Top" />
    <Setter Property="Width" Value="200" />
    <Setter Property="Tag" Value="Red"/><!-- For the sake of having a default -->

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="custom:GradientButton">
                <Grid>
                    <Grid.Background>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                             <GradientStop Color="{TemplateBinding Tag}" Offset="0"/>
                             <GradientStop Color="Gray" Offset="1"/>
                        </LinearGradientBrush>
                    </Grid.Background>

                    <ContentPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and that should do ya up nicely, hope this helps. :)

I've discovered a workaround: if, instead of TemplateBinding, I use a RelativeSource binding, then my button renders as you would expect:

Color="{Binding RelativeSource={RelativeSource AncestorType=custom:GradientButton},Path=BackgroundColor}"

Of course, this makes nary a whit of sense. I'm convinced that this is a bug (or at least one of those things that man was not meant to know).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top