Description:
I have a custom content control and I am trying to enable some external settings via dependency properties. Basically it's a decorator panel with two grid rows, upper one is the header, the lower one is the content (via ContentPresenter
).
There are 3 items that are bound to the template (via TemplateBinding), HeaderHeight
, TextSize
and Header
(each of them has its dependency property of an appropriate type).
Problem:
While two of the bindings work perfectly (even in design-time), the third one does not. The FontSize="{TemplateBinding TextSize}"
and the Text="{TemplateBinding Header}"
bindings work, but the <RowDefinition Height="{TemplateBinding HeaderHeight}" />
does not work.
The grid splits the rows' heights 50/50, no matter which value I set the HeaderHeight property to. It does not even take the default value from the DP metadata.
Question: What is the problem with this scenario? Why do the other two bindings work with no problems and this one behaves as if there is no binding at all?
Note:
If I set DataContext = this
in the constructor and replace {TemplateBinding HeaderHeight}
with {Binding HeaderHeight}
, the problem disappears and it works as intended. But I'd like to know why I don't need to do the same thing with other bindings to make them work.
XAML (Themes/Generic.xaml):
<Style TargetType="local:KaiPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KaiPanel">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding HeaderHeight}" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Border>
<TextBlock FontSize="{TemplateBinding TextSize}"
Text="{TemplateBinding Header}" />
</Border>
</Grid>
<Grid Grid.Row="1">
<Border>
<ContentPresenter />
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Content Control (C#):
public class KaiPanel : ContentControl
{
public KaiPanel()
{
this.DefaultStyleKey = typeof(KaiPanel);
}
public static readonly DependencyProperty TextSizeProperty =
DependencyProperty.Register("TextSize", typeof(double), typeof(KaiPanel), new PropertyMetadata(15.0));
public double TextSize
{
get { return (double)GetValue(TextSizeProperty); }
set { SetValue(TextSizeProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(String), typeof(KaiPanel), new PropertyMetadata(""));
public String Header
{
get { return (String)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderHeightProperty =
DependencyProperty.Register("HeaderHeight", typeof(GridLength), typeof(KaiPanel), new PropertyMetadata(new GridLength(40)));
public GridLength HeaderHeight
{
get { return (GridLength)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
}
Custom Control usage (XAML):
<UserControl ...>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel x:Name="buttonsStackPanel" Grid.Column="0" VerticalAlignment="Center">
<!-- Some buttons here -->
</StackPanel>
<Grid Grid.Column="1">
<controls:KaiPanel x:Name="contentPanel">
<navigation:Frame x:Name="contentFrame" Source="KP">
<navigation:Frame.UriMapper>
<uriMapper:UriMapper>
<uriMapper:UriMapping Uri="KP" MappedUri="/Views/Kornelijepetak.xaml" />
<uriMapper:UriMapping Uri="KAI" MappedUri="/Views/KaiNetwork.xaml" />
</uriMapper:UriMapper>
</navigation:Frame.UriMapper>
</navigation:Frame>
</controls:KaiPanel>
</Grid>
</Grid>
</UserControl>
Sadly it seems what you're attempting to do requires more than just a single data binding. RowDefinition isn't a subclass of FrameworkElement, and it doesn't match any of the other criteria specified in the MSDN Silverlight data binding documentation, so it can't be used as the target of a binding.
What you want to do is possible, but unfortunately it involves a little more code.
Firstly, add a field for the main grid (I've called it mainGrid
) to your KaiPanel
class. Then, override the OnApplyTemplate
method in this class to grab the main Grid
from the template and keep a reference to it in your mainGrid
field:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mainGrid = GetTemplateChild("LayoutRoot") as Grid;
SetHeaderRowHeight();
}
This calls a method that updates the height of the first row of the grid. That method is as follows:
private void SetHeaderRowHeight()
{
if (mainGrid != null)
{
mainGrid.RowDefinitions[0].Height = HeaderHeight;
}
}
I admit I'm not 100% sure that OnApplyTemplate
is called after the DPs are set. It seems that this is the case (a quick test seemed to confirm this), but all I could find to back this up was this post on the Silverlight forums. If you find that this isn't the case, you'll need to register a PropertyChangedCallback on the HeaderHeight
DP that will also call this SetHeaderRowHeight
method.
See also http://forums.silverlight.net/forums/t/76992.aspx#183089.