views:

763

answers:

5

Hi, I'm having a heck of a time trying to template bind the StrokeThickness of a rectangle.

My goal is to allow a user of my custom control to set a property called SelectedBorderThickness which will, in fact, set the StrokeThickness of a rectangle.

I thought I understood templating but I guess I really don't.

If I do this:

<Rectangle x:Name="myRect" Height="100" Width="100" Stroke="Black" SelectedBorderThickness="5" />

Can someone please show me how to write the Style elements to get this to work?

+1  A: 

You should add more details to the question and people will be able to help you more easily. I think I have figured out what you want though.

You are looking to make a custom templated silverlight control, containing a bunch of elements incluiding a rectangle in its template. You would like a user to be able to set the thickness of that rectangle inside the control with a property on the control itself. From what you put above, I don't know how much you have written in your code -- so I will just post a nearly complete example of what you are after.

First I created a templated custom control in visual studio, and added the new dependancy property we want a user to be able to set:

public class TestControl : Control
{
    static public DependencyProperty SBTProperty { get; set; }
    static TestControl()
    {
        SBTProperty = DependencyProperty.Register("SelectedBorderThickness", typeof(double), typeof(TestControl),null);
    }

    public TestControl()
    {
        this.DefaultStyleKey = typeof(TestControl);
    }

    public double SelectedBorderThickness
    {
        get { return (double)GetValue(SBTProperty); }
        set { SetValue(SBTProperty, value); }
    }
}

Then I set up the template in Generic.xaml (for my example the only thing I have in my control is the rectangle since I don't know what you want in there):

<Style TargetType="local:TestControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:TestControl">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Rectangle Fill="Bisque" Stroke="Black" StrokeThickness="{TemplateBinding SelectedBorderThickness}"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now I am all set up to use it from xaml in other parts of my application. For my example, I put one right in the center of my MainPage:

<local:TestControl SelectedBorderThickness="75"></local:TestControl>

EDIT: After reading your code below, I see now what the problem is. You're trying to do a template binding, but the way you have it it's going to try to bind to the current template, which is the template for listboxitem and not your custom listbox. What you really want in this situation is to do a RelativeBinding with FindAncestor to jump up the tree to the template of your custom listbox, but MS hasn't yet implemented that kind of binding in Silverlight (even though it's pretty common in WPF). Luckily in your specific situation we can finagle the right object through the path in a TemplatedParent binding, without having to write a bunch of messy codebehind to emulate an ancestor binding:

StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content.Parent.SelectedBorderThickness}"

Dump that into the Rectangle in your template posted above and it should work -- it will access the content of the ListBoxItem (which is whatever you are displaying), and then access that objects Parent (which will be your custom listbox). From there we just hit up the property we set up before.

If you want a cleaner solution, join the chorus of us asking MS to implement ancestor binding in Silverlight.

David Hay
Since I'm limited in the amount of text I can show and format in the comments, see my detailed comment below.
beaudetious
By the way, thanks for detailed answer.
beaudetious
David, it compiles and doesn't throw an error when I run MainPage.xaml, but the selected borders aren't changing. One step forward though.
beaudetious
Are you sure you have your properties (and the other stuff not posted above) set up correctly? I put everything you posted below into my solution and it works fine. Take a look at my code, I have zipped the whole solution and put it up at my website http://www.dlhcode.com/dl/SilverlightApplication3.zip
David Hay
A: 

Here's the problem section, it's when I'm attempting to style the ItemContainerStyle for my custom control which derives from a ListBox:

<Setter Property="ItemContainerStyle">
    <Setter.Value>
        <Style TargetType="ListBoxItem">
            <Setter Property="Template">
                <Setter.Value>
             <ControlTemplate TargetType="ListBoxItem">
                 <Grid Background="{TemplateBinding Background}">
              <!-- VSM stuff removed for clarity -->
              <ContentPresenter
                   x:Name="contentPresenter"
                   Content="{TemplateBinding Content}"
                   ContentTemplate="{TemplateBinding ContentTemplate}"
                   HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                   Margin="{TemplateBinding Padding}"/>
               <Rectangle x:Name="FocusVisualElement" 
                   Stroke="Goldenrod" 
            StrokeThickness="{TemplateBinding SelectedBorderThickness}" 
            Visibility="Collapsed" 
            RadiusX="1" 
            RadiusY="1" />
                 </Grid>
             </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Setter.Value>
</Setter>

The problems is that when I set StrokeThickness = {TemplateBinding SelectedBorderThickness} on the Rectangle and then try to use the control in a test app, I get a ParserError:

Message: Unknown attribute StrokeThickness on element Rectangle

If I hardcode the StrokeThickness to 3 (or whatever), it parses fine and I can view the test app.

In the end, all I'm really trying to do is create a property that shows up in Intellisense so that my (eventual) end users of my custom control can change the color and border thickness, radius, etc. of the highlight on a hovered and selected ListBoxItem in a dynamically bound custom ListBox. It shouldn't be this dang hard.

beaudetious
See my edit above -- the funky binding posted there should work in your application.
David Hay
See my new comment above, posted my working solution using your markup.
David Hay
A: 

The dang comments are too restricted. I'm not trying to answer my own question (I wish I could).

David, your code works fine when you add ListBoxItems statically. When adding them dynamically, the thickness doesn't change. To test this out, I added a new TestControl in MainPage:

<StackPanel>
  <local:TestControl SelectedBorderThickness="9" x:Name="h1n1">
    <TextBlock Text="Honk1"></TextBlock>
    <TextBlock Text="Honk2"/>
  </local:TestControl>
  <local:TestControl x:Name="SwineFlu" SelectedBorderThickness="20" />
</StackPanel>

In the code-behind I added:

ObservableCollection<string> test = new ObservableCollection<string>();
test.Add("Hi David");
test.Add("Hello World");
SwineFlu.ItemsSource = test;
beaudetious
A: 

David,

I stumbled across the following error messages when doing some more testing on your sample project:

System.Windows.Data Error: BindingExpression path error: 'Parent' property not found on 'Hi David' 'System.String' (HashCode=1775239720). BindingExpression: Path='Content.Parent.SelectedBorderThickness' DataItem='System.Windows.Controls.ListBoxItem' (HashCode=24827179); target element is 'System.Windows.Shapes.Rectangle' (Name='FocusVisualElement'); target property is 'StrokeThickness' (type 'System.Double')..
System.Windows.Data Error: BindingExpression path error: 'Parent' property not found on 'Hello World' 'System.String' (HashCode=1411332840). BindingExpression: Path='Content.Parent.SelectedBorderThickness' DataItem='System.Windows.Controls.ListBoxItem' (HashCode=31201899); target element is 'System.Windows.Shapes.Rectangle' (Name='FocusVisualElement'); target property is 'StrokeThickness' (type 'System.Double')..

Do you think you (or someone else out there) could help me figure out how to set the border thickness on the Stroke property for a custom listbox that is dynamically bound?

Thanks.

Beaudetious

beaudetious
+1  A: 

hi try this one

<Setter Property="ItemContainerStyle">
    <Setter.Value>
        <Style TargetType="ListBoxItem">
            <Setter Property="SelectedBorderThickness" Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Grid Background="{TemplateBinding Background}">
                            <!-- VSM stuff removed for clarity -->
                            <ContentPresenter
                                 x:Name="contentPresenter"
                                 Content="{TemplateBinding Content}"
                                 ContentTemplate="{TemplateBinding ContentTemplate}"
                                 HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                 Margin="{TemplateBinding Padding}"/>
                             <Rectangle x:Name="FocusVisualElement" 
                                 Stroke="Goldenrod" 
                                 StrokeThickness="{TemplateBinding SelectedBorderThickness}" 
                                 Visibility="Collapsed" 
                                 RadiusX="1" 
                                 RadiusY="1" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Setter.Value>
</Setter>
Vinod
I've moved on from there and am now using a custom listboxitem which is working for me. But I will still try your suggestion in my test app to see if it works.
beaudetious
Nope, didn't work. :(
beaudetious