Another Way to Undo Implicit Styles (Revisited)

|

In my previous monthly long installment, I showed another way to undo the implicit style, I demonstrated how to leverage the InheritanceBehaviors to break the style lookup chains so you can have a portion of element tree which will directly pick up the system default theme styles. in this post, I will show a different approach to this problem based on the trick Mike Hillberg mentioned in his blog article.

In Mike's original article, he showed that you can undo implicit style by explicitly set the interesting Element's Style property to null, based on this concept, I come up with the following code:

using System;
using System.IO;
using System.Windows;
using System.Diagnostics;
using System.Windows.Markup;
using System.Windows.Controls;
using System.Collections.Generic;

namespace Sheva.Windows.Components
{
    public class StyleManager
    {
        public static DependencyProperty IsDefaultStyleEnabledProperty = DependencyProperty.RegisterAttached(
            "IsDefaultStyleEnabled",
            typeof(Boolean),
            typeof(StyleManager),
            new FrameworkPropertyMetadata(false,
                                          FrameworkPropertyMetadataOptions.Inherits,
                                          new PropertyChangedCallback(OnIsDefaultStyleEnabledPropertyChanged)));
        private static Dictionary<FrameworkElement, Style> oldStyleStore = new Dictionary<FrameworkElement, Style>();

        public static void SetIsDefaultStyleEnabled(FrameworkElement element, Boolean value)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            if (value)
            {
                AddStyleToStore(element);
            }

            element.SetValue(IsDefaultStyleEnabledProperty, value);
        }


        private static void OnIsDefaultStyleEnabledPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = sender as FrameworkElement;
            if (element == null) return;

            if ((Boolean)e.NewValue)
            {
                if (!(Boolean)e.OldValue)
                {
                    AddStyleToStore(element);
                }

                element.Style = null;
            }
            else
            {
                if (oldStyleStore.ContainsKey(element))
                {
                    element.Style = oldStyleStore[element];
                }
            }
        }

        private static void AddStyleToStore(FrameworkElement element)
        {
            Debug.Assert(element != null, "parameter 'element' cannot be null");
            if (!oldStyleStore.ContainsKey(element))
            {
                if (element.ReadLocalValue(FrameworkElement.StyleProperty) == DependencyProperty.UnsetValue)
                {
                    Style style = element.TryFindResource(element.GetType()) as Style;
                    oldStyleStore.Add(element, style);
                }
                else
                {
                    oldStyleStore.Add(element, element.Style);
                }
            }
        }
    }
}

From the above code, you can see that I've defined a inheritable attached DependencyProperty so when this attached DP is applied in the parent element, all the children elements within its containing scope will has this attached DP inherited, and undo their implicit styles individually. The cloning of Style property using CreateStyleClone helper method is really necessary since when a style is explicitly set, and you want to turn it off, you should first cache the explicit style in the oldStyleStore, and then set the Style to null, the style within the oldStyleStore dictionary will also be nullified if you don't make a copy of the original style.

The following XAML shows how to use the StyleManager class:

<Window x:Class="UndoImplicitStyles.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="Undo Implicit Styles" Height="300" Width="300"
       xmlns:local="clr-namespace:Sheva.Windows.Components">
   <Window.Resources>
       <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
           <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.MessageFontFamilyKey}}" />
           <Setter Property="FontSize" Value="{DynamicResource {x:Static SystemFonts.MessageFontSizeKey}}" />
           <Setter Property="FontStyle" Value="{DynamicResource {x:Static SystemFonts.MessageFontStyleKey}}" />
           <Setter Property="FontWeight" Value="{DynamicResource {x:Static SystemFonts.MessageFontWeightKey}}" />
           <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
           <Setter Property="HorizontalContentAlignment" Value="Center" />
           <Setter Property="VerticalContentAlignment" Value="Center" />
           <Setter Property="ClipToBounds" Value="True" />
           <Setter Property="Padding" Value="2" />
           <Setter Property="Margin" Value="10" />
           <Setter Property="Height" Value="30" />
           <Setter Property="Template">
               <Setter.Value>
                   <ControlTemplate TargetType="{x:Type Button}">
                       <Grid>
                           <Rectangle x:Name="GelBackground" Opacity="1" RadiusX="4" RadiusY="4" Stroke="Black" StrokeThickness="1">
                               <Rectangle.Fill>
                                   <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                       <LinearGradientBrush.GradientStops>
                                           <GradientStop Offset="0" Color="White" />
                                           <GradientStop Offset="1" Color="#99ccff" />
                                       </LinearGradientBrush.GradientStops>
                                   </LinearGradientBrush>
                               </Rectangle.Fill>
                           </Rectangle>
                           <Rectangle x:Name="GelShine" Margin="2,2,2,0" VerticalAlignment="Top" RadiusX="6" RadiusY="6" Opacity="0" Stroke="Transparent" Height="15px">
                               <Rectangle.Fill>
                                   <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                       <LinearGradientBrush.GradientStops>
                                           <GradientStop Offset="0" Color="#ccffffff" />
                                           <GradientStop Offset="1" Color="Transparent" />
                                       </LinearGradientBrush.GradientStops>
                                   </LinearGradientBrush>
                               </Rectangle.Fill>
                           </Rectangle>
                           <ContentPresenter x:Name="ContentSite" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
                       </Grid>
                       <ControlTemplate.Triggers>
                           <Trigger Property="IsMouseOver" Value="true">
                               <Setter Property="Rectangle.Fill" Value="#99ccff" TargetName="GelBackground" />
                               <Setter Property="Rectangle.Opacity" Value="1" TargetName="GelShine" />
                           </Trigger>
                       </ControlTemplate.Triggers>
                   </ControlTemplate>
               </Setter.Value>
           </Setter>
       </Style>
   </Window.Resources>
   <StackPanel>
       <Button Content="Click Me" />
       <CheckBox Content="Undo Implicit Style" Name="checkBox"/>
       <StackPanel local:StyleManager.IsDefaultStyleEnabled="{Binding ElementName=checkBox, Path=IsChecked}">
           <Button Content="Click Me" />
           <StackPanel>
               <Button Content="Click Me" />
               <StackPanel>
                   <Button Content="Click Me" />
               </StackPanel>
           </StackPanel>
       </StackPanel>
       <Button Content="Click Me" />
   </StackPanel>
</
Window>

When you run above XAML code, you will find that when checking and unchekding the CheckBox, the style of Buttons within the containing StackPanel parent will by toggled between default system style and the implicit style set in the resource.

Disclaimer: The above solution just gives you a way to tackle the problem of undoing implicit styles, and it's not an elegant or performant way of doing things, I just come up with it here for completeness rather than as a recommendation. if you want to have a portion of your element tree to just pick up the system default styles, I strongly recommend you to use the approach I demonstrated in my previous article, since presumably this approach is also taken by the Expression Blend.

Attachment: UndoImplicitStyles.zip

0 comments: