Размещение элемента наложения относительно области содержимого TabControl в WPF

Вот чего я хочу добиться:

При определенных обстоятельствах поверх всех других элементов окна должен быть виден оверлей. Эта часть рабочая.

Основным контейнером в Windows является TabControl, а наложение имеет поле, достаточно большое для отображения под заголовками вкладок (там, где расположена область содержимого).

Проблема начинается, когда заголовки вкладок охватывают две строки (или когда изменяется размер шрифта). Есть ли способ разместить наложение относительно области, в которой будет начинаться контентная часть TabControl?


person Onur    schedule 30.06.2015    source источник


Ответы (3)


Я предлагаю вам взглянуть на вставку вашего оверлея в TabControl ControlTemplate, чтобы он стал частью визуального дерева, в котором нарисован TabItem, и соответственно изменит размер.

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

<ControlTemplate TargetType="{x:Type TabControl}">
    <StackPanel>
        <TabPanel />
        <Grid>

            <ContentPresenter x:Name="ContentTop" 
                        HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
                        VerticalAlignment="{TemplateBinding VerticalAlignment}"
                        Margin="{TemplateBinding Padding}"
                        Cursor="{TemplateBinding Cursor}" />

            <YourOverlayControl x:Name="OverlayControl"></YourOverlayControl>

        </Grid>
    </StackPanel>
</ControlTemplate>

Главное, что ваш элемент управления наложением представлен поверх ContentPresenter (внутри элемента управления сеткой). ContentPresenter в этом шаблоне будет отображать ваш контент TabItem.

Если вы еще не определили шаблон настраиваемого элемента управления для TabControl, ознакомьтесь с этой статьей на MSDN, чтобы увидеть полнофункциональный пример.

person olitee    schedule 30.06.2015
comment
Ваше решение работает, но мне кажется, что это слишком много для этой простой задачи. Тем более, что я не смог найти шаблон элемента управления TabControl по умолчанию для .net 4.5. - person Onur; 01.07.2015
comment
Проблема в том, что нет простого способа привязать к TabPanel в TabControl, чтобы определить его фактическую высоту ... чего вы действительно хотите достичь? Другой подход может заключаться в создании настраиваемой реализации TabControl, которая предоставляет высоту TabPanel. Добавлю еще один ответ. - person olitee; 01.07.2015

Другой подход может заключаться в создании настраиваемой реализации TabControl, который предоставляет новое свойство зависимости: TabPanelActualHeight.

Предполагается, что вы используете шаблон элемента управления по умолчанию, но он также должен работать для любого шаблона, который включает TabPanel (TabPanel не является обязательной частью шаблона TabControl).

public class CustomTabControl : TabControl
{
    private static readonly DependencyPropertyKey ReadOnlyPropPropertyKey
                            = DependencyProperty.RegisterReadOnly("ReadOnlyProp", typeof(double), typeof(CustomTabControl),
                                new FrameworkPropertyMetadata((double)0,
                                    FrameworkPropertyMetadataOptions.None));

    public static readonly DependencyProperty TabPanelActualHeightProperty = ReadOnlyPropPropertyKey.DependencyProperty;

    public double TabPanelActualHeight
    {
        get { return (double)GetValue(TabPanelActualHeightProperty); }
        protected set { SetValue(ReadOnlyPropPropertyKey, value); }
    }

    private TabPanel _tabPanel = null;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _tabPanel = this.GetChildOfType<TabPanel>();

        if (_tabPanel != null)
        {
            TabPanelActualHeight = _tabPanel.ActualHeight;
            _tabPanel.SizeChanged += TabPanelOnSizeChanged;
        }

    }

    private void TabPanelOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
    {
        TabPanelActualHeight = _tabPanel.ActualHeight;
    }
}

ПРИМЕЧАНИЕ. Этот пример зависит от метода расширения GetChildOfType<T> из этого ТАК пост.

Теперь вы можете просто привязаться к свойству TabPanelActualHeight настраиваемого TabControl, чтобы помочь вам смещать / позиционировать наложение.

person olitee    schedule 01.07.2015
comment
Спасибо за этот ответ. Это вдохновило меня на создание присоединенного свойства, которое работает примерно так же, как ваше свойство зависимостей в производном классе. - person Onur; 01.07.2015
comment
Ах, да, еще один выход - присоединенная собственность. Спасибо, что поделились своим решением. - person olitee; 01.07.2015

Вдохновленный этим ответом, я создал прикрепленное свойство, которое позволяет мне извлекать высоту панели вкладок. Я использую это значение, чтобы создать запас, необходимый для моего наложения. Я предпочитаю эту версию, потому что

  • Мне не нужно создавать новый стиль / изменять стиль по умолчанию
  • Не нужно создавать новый элемент управления только для одного небольшого аспекта

Вот код прикрепленного свойства.

public static class TabPanelHeaderHeight
{
    private const double InitialValue = -1.0;

    private static readonly DependencyProperty HeaderHeightProperty =
        DependencyProperty.RegisterAttached(name: "HeaderHeight", propertyType: typeof(double), ownerType: typeof(TabPanelHeaderHeight),
        defaultMetadata: new FrameworkPropertyMetadata(defaultValue: InitialValue, flags: FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, propertyChangedCallback: OnHeaderHeightChanged));

    public static double GetHeaderHeight(UIElement element)
    {
        return (double)element.GetValue(HeaderHeightProperty);
    }
    public static void SetHeaderHeight(UIElement element, double value)
    {
        element.SetValue(HeaderHeightProperty,value);
    }

    private static void OnHeaderHeightChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        var target = obj as TabControl;
        if (target == null)
            return;

        // we hijack the on value changed event to register our event handler to the value we're really interested in
        // but we want to do this only once, so we check if the value is the (invalid) initial value and change the value afterwards to register the event listener only once.
        if ((double)args.OldValue == InitialValue)
        {
            var tp = target.GetChildOfType<TabPanel>();

            tp.SizeChanged += (sender, eventArgs) => { TargetSizeChanged(target,tp); };
            TargetSizeChanged(target,tp);
        }
    }

    private static void TargetSizeChanged(TabControl target, TabPanel tp)
    {
        SetHeaderHeight(target, tp.ActualHeight);
    }

    public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
    {
        if (depObj == null) return null;

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            var child = VisualTreeHelper.GetChild(depObj, i);

            var result = (child as T) ?? GetChildOfType<T>(child);
            if (result != null) return result;
        }
        return null;
    }

}

помощник GetChildOfType взят из этого сообщения

Его можно использовать в xaml следующим образом:

<TabControl  yourNs:TabPanelHeaderHeight.HeaderHeight="{Binding Path=HeaderHeight}" >
...

Либо используйте преобразователь значений для создания поля для наложения, либо выполните это в модели представления.

person Onur    schedule 01.07.2015