TemplateBinding для RowDefinition.Height игнорируется в ContentControl

Описание: у меня есть настраиваемый элемент управления содержимым, и я пытаюсь включить некоторые внешние параметры через свойства зависимостей. По сути, это панель декоратора с двумя строками сетки, верхняя — заголовок, нижняя — содержимое (через ContentPresenter).

Есть 3 элемента, которые привязаны к шаблону (через TemplateBinding), HeaderHeight, TextSize и Header (каждый из них имеет свое свойство зависимости соответствующего типа).

Проблема. Две привязки работают идеально (даже во время разработки), а третья — нет. Привязки FontSize="{TemplateBinding TextSize}" и Text="{TemplateBinding Header}" работают, но <RowDefinition Height="{TemplateBinding HeaderHeight}" /> не работает.

Сетка разделяет высоту строк 50/50, независимо от того, какое значение я установил для свойства HeaderHeight. Он даже не берет значение по умолчанию из метаданных DP.

Вопрос. В чем проблема с этим сценарием? Почему две другие привязки работают без проблем, а эта ведет себя так, как будто привязки вообще нет?

Примечание. Если я установлю DataContext = this в конструкторе и заменю {TemplateBinding HeaderHeight} на {Binding HeaderHeight}, проблема исчезнет, ​​и все будет работать, как задумано. Но я хотел бы знать, почему мне не нужно делать то же самое с другими привязками, чтобы заставить их работать.

XAML (темы/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>

Управление контентом (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); }
    }
}

Использование пользовательского элемента управления (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>

person Kornelije Petak    schedule 26.06.2011    source источник
comment
У вас есть пример вашего Xaml, использующего KaiPanel, чтобы мы могли воспроизвести проблему? Пока что все выглядит нормально, но достаточно одной маленькой детали, чтобы что-то сломать.   -  person Gone Coding    schedule 26.06.2011
comment
@ Я обновил код, но это не имеет значения. Это не сработает, даже если вы разместите один тег ‹controls:KaiPanel /›. Он должен принимать значение по умолчанию из DP, но это не так. Пробовал размещать внутри разный контент, но это не беда - он всегда ведет себя одинаково: 50/50.   -  person Kornelije Petak    schedule 26.06.2011


Ответы (2)


К сожалению, кажется, что то, что вы пытаетесь сделать, требует больше, чем просто привязка данных. RowDefinition не является подклассом FrameworkElement и не соответствует ни одному из других критериев, указанных в документация по привязке данных MSDN Silverlight, поэтому ее нельзя использовать в качестве цели привязки.

То, что вы хотите сделать, возможно, но, к сожалению, это требует немного больше кода.

Во-первых, добавьте поле для основной сетки (я назвал его mainGrid) в свой класс KaiPanel. Затем переопределите метод OnApplyTemplate в этом классе, чтобы получить основной Grid из шаблона и сохранить ссылку на него в поле mainGrid:

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        mainGrid = GetTemplateChild("LayoutRoot") as Grid;
        SetHeaderRowHeight();
    }

Это вызывает метод, который обновляет высоту первой строки сетки. Этот метод заключается в следующем:

    private void SetHeaderRowHeight()
    {
        if (mainGrid != null)
        {
            mainGrid.RowDefinitions[0].Height = HeaderHeight;
        }
    }

Я признаю, что не уверен на 100%, что OnApplyTemplate вызывается после установки DP. Кажется, это так (быстрый тест, казалось, подтвердил это), но все, что я смог найти, чтобы подтвердить это, это это сообщение на форумах Silverlight. Если вы обнаружите, что это не так, вам необходимо зарегистрировать PropertyChangedCallback на HeaderHeight DP, который также будет вызывать этот метод SetHeaderRowHeight.

См. также http://forums.silverlight.net/forums/t/76992.aspx#183089.

person Luke Woodward    schedule 26.06.2011
comment
Вы говорите, что его нельзя использовать в привязке, но он работает в обычной привязке (не TemplateBinding). Проверьте мою заметку еще раз. Если DataContext=this установлен в конструкторе и в шаблоне, если я устанавливаю: {Binding HeaderHeight} вместо {TemplateBinding HeaderHeight}, тогда это работает. Очень странное поведение. У меня есть еще один элемент управления, который работает даже с TemplateBinding, что делает его еще более странным. - person Kornelije Petak; 27.06.2011
comment
Исправление: теперь я посмотрел на свой другой элемент управления. Он не работает с привязкой шаблона, но работает с обычной привязкой (с DataContext, установленным на сам элемент управления). - person Kornelije Petak; 27.06.2011

Вместо этого используйте RelativeSource и TemplatedParent:

<RowDefinition Height="{Binding RelativeSource={RelativeSource TemplatedParent},
 Path=HeaderHeight}" />

Здесь объясняется разница между TemplateBinding и RelativeSource TemplatedParent: WPF TemplateBinding vs RelativeSource TemplatedParent

person Lord Tasci    schedule 21.04.2021