0

WPF. Consider the following style with template, and how it affects padding on a MenuItem. (These are padding-related lines extracted from a style in MaterialDesignTheme.Menu.xaml of MaterialDesignThemes.Wpf in github from http://materialdesigninxaml.net/.)

<Style TargetType="{x:Type MenuItem}" x:Key="MaterialDesignMenuItem" BasedOn="{x:Null}">
    <Setter Property="Padding" Value="24 0"></Setter>
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type MenuItem}">
                <ControlTemplate.Resources>
                </ControlTemplate.Resources>
                <Grid ClipToBounds="True">
                </Grid>
                <ControlTemplate.Triggers>
                    <!--#region Roles Triggers -->
                    <Trigger Property="Role" Value="TopLevelHeader">
                        <Setter Property="Padding" Value="16 0"/>
                    </Trigger>
                    <Trigger Property="Role" Value="TopLevelItem">
                        <Setter Property="Padding" Value="16 0"/>
                    </Trigger>
                    <!--#endregion-->
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It is easy to override the default padding line

<Setter Property="Padding" Value="24 0"></Setter>

like so:

            <Style TargetType="MenuItem" BasedOn="{StaticResource MaterialDesignMenuItem}">
                <Setter Property="Padding" Value="4 0"></Setter>
            </Style>

What I haven't been able to find, is how to define the two TopLevel... Triggers such that their common padding value "16 0" can be overridden.

In the actual code, these triggers do more, so I don't want to totally override the triggers; I'd have to duplicate the rest of the trigger code.

I'd like to have the original style specify a default for these, which I can override. Something like:

<Style TargetType="{x:Type MenuItem}" x:Key="MaterialDesignMenuItem" BasedOn="{x:Null}">
    <Setter Property="Padding" Value="24 0"></Setter>
    <Setter Property="TopPadding" Value="16 0"></Setter>
...

Then override:

            <Style TargetType="MenuItem" BasedOn="{StaticResource MaterialDesignMenuItem}">
                <Setter Property="Padding" Value="4 0"></Setter>
                <Setter TopPadding="Padding" Value="4 0"></Setter>
            </Style>

But of course there is no "TopPadding" property on MenuItem, so this won't compile.

How do I create this new property within the original style, refer to it in the template - in the two triggers, and then override it in my customized style?

Possibly related answers:
Bind to resource keys on each item
Bind to attached property

Worst case, I could subclass MenuItem, and make a template that only applies to that subclass (to add "TopPadding" property). I'd prefer not to do that, as this is part of a complex library; I'd like to have a template that will work with any MenuItem. I think that means any attached property would have to be optional (a MenuItem might not have it.) I'm not sure how the template would refer to an attached property, nor how to set a default if it is missing.

IMPORTANT: I don't want a hack that uses Tag property; I want a scaleable approach that somehow adds an appropriate property or resource.


UPDATE

I just found this answer; I did not realize that attached properties don't have to be defined in the original class - can define one without subclassing MenuItem.
I'm not clear whether I can set a default value in case this attached property is missing, but I am attempting to do so now.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196

1 Answers1

0

NOT WORKING attempt, using idea from this answer:

Define an attached property in a c# class in that library:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace MaterialDesignThemes.Wpf
{
    public static class ThemeAssist
    {
        // ...

        #region TopPadding AttachedProperty
        public static DependencyProperty TopPaddingProperty =
            DependencyProperty.RegisterAttached("TopPadding",
                typeof(Thickness),
                typeof(ThemeAssist),
                new PropertyMetadata(new Thickness(12, 0, 12, 0)));
        public static void SetTopPadding(DependencyObject obj, Thickness value)
        {
            obj.SetValue(TopPaddingProperty, value);
        }
        public static Brush GetTopPadding(DependencyObject obj)
        {
            return (Brush)obj.GetValue(TopPaddingProperty);
        }
        #endregion
    }
}

Attempting to use that attached property in the template:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf"
                    xmlns:converters="clr-namespace:MaterialDesignThemes.Wpf.Converters">

    <!-- ... -->

    <Style TargetType="{x:Type MenuItem}" x:Key="MaterialDesignMenuItem" BasedOn="{x:Null}">
        <Setter Property="Padding" Value="24 0"/>
        <Setter Property="OverridesDefaultStyle" Value="True"/>
        <Setter Property="wpf:ThemeAssist.TopPadding" Value="16 0 16 0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type MenuItem}">
                    <ControlTemplate.Resources>
                    </ControlTemplate.Resources>
                    <Grid ClipToBounds="True">
                    </Grid>
                    <ControlTemplate.Triggers>
                        <!--#region Roles Triggers -->
                        <Trigger Property="Role" Value="TopLevelHeader">
                            <!-- <Setter Property="Padding" Value="16 0"/> -->
                            <Setter Property="Padding" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:ThemeAssist.TopPadding)}"/>
                        </Trigger>
                        <Trigger Property="Role" Value="TopLevelItem">
                            <!--<Setter Property="Padding" Value="16 0"/>-->
                            <Setter Property="Padding" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:ThemeAssist.TopPadding)}"/>
                        </Trigger>
                        <!--#endregion-->
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


Usage (in a different assembly):

    <StackPanel DockPanel.Dock="Top">
        <StackPanel.Resources>
            <Style TargetType="MenuItem" BasedOn="{StaticResource MaterialDesignMenuItem}">
                <Setter Property="Padding" Value="24 0 24 0"/>
                <!--<Setter Property="wpf:ThemeAssist.TopPadding" Value="12 0 12 0"/>-->
                <Setter Property="Background" Value="Aqua"></Setter>
            </Style>
        </StackPanel.Resources>
        <Menu x:Name="MainMenu" IsMainMenu="True" Visibility="Collapsed">
            <MenuItem Header="_File">

WRONG RESULT: TopLevelHeader/TopLevelItem MenuItem Padding is always (0,0,0,0). Neither the default on the attached property definition (12, ..) nor the value on style setter <Setter Property="wpf:ThemeAssist.TopPadding" Value="16 0 16 0"/> have any effect.

Importantly, the final style's <Setter Property="Padding" Value="24 0 24 0"/> does get overridden by the trigger; it seems that the trigger is not finding an attached property, so has reset Padding to 0.


If I restore the original "16 0" value in trigger's padding, the menuitems do get that padding.


I also tried adding TopPadding to the final style, but did not succeed in finding a syntax that did not complain in visual designer. (I ran the program anyway, but still no effect.) My attempted syntax:

<UserControl x:Class="GISWPF.Dialogs"
         xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
         ...
>
    ...
            <Style TargetType="MenuItem" BasedOn="{StaticResource MaterialDesignMenuItem}">
                <Setter Property="wpf:ThemeAssist.TopPadding" Value="12 0 12 0"/>
            </Style>

gave design-time tooltip:

'' is not a valid value for the 'MaterialDesignThemes.Wpf.ThemeAssist.TopPadding' property on a Setter.

Its odd that I say Value="12 0 12 0", yet the error message shows a blank value ''. Fails to apply String to Thickness converter?

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196