Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF
Article

WPF Explorer Bar

Rate me:
Please Sign up or sign in to vote.
4.67/5 (21 votes)
5 Oct 2008MIT3 min read 106.2K   2.6K   85   18
A WPF explorer bar implementation.

XP_Metallic.pngOdyssey

Introduction

This is a WPF Explorer Bar similar to the Explorer bar in Windows XP. An explorer bar usually contains one ore more collapsible panels, as shown above.

Using the code

WPF offers very nice animation features to allow almost anything you can imagine. Unfortunately, for a generic panel that supports animation while expanding/collapsing, it's not just as simple as defining DoubleAnimation to the Height property. Although it would work for ScaleTransform.ScaleY, the effect would be different to what we see in XP. Therefore, I use a custom Decorator control, which, in a few words, is a panel that can contain only one child. The AnimationDecorator has an IsExpanded property that specifies whether the decorator is expanded or collapsed. To perform the animation, I added a helper property named YOffset that will be animated. YOffset has a range from 0 to the ActualHeight of the decorator, and is used at ArrangeOverride and MeasureOverride to perform the animation. The code looks like:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Odyssey.Controls
{

public class AnimationDecorator : Decorator
{

    static AnimationDecorator()
    {
    }

    public AnimationDecorator()
    : base()
    {
        ClipToBounds = true;
    }

    /// <summary>
    /// Specify whether to apply opactiy animation.
    /// </summary>
    public bool OpacityAnimation
    {
        get { return (bool)GetValue(OpacityAnimationProperty); }
        set { SetValue(OpacityAnimationProperty, value); }
    }

    public static readonly DependencyProperty OpacityAnimationProperty =
            DependencyProperty.Register("OpacityAnimation", 
            typeof(bool), 
            typeof(AnimationDecorator),
            new UIPropertyMetadata(true));

    /// <summary>
    /// Gets or sets whether the decorator is expanded or collapsed.
    /// </summary>
    public bool IsExpanded
    {
        get { return (bool)GetValue(IsExpandedProperty); }
        set { SetValue(IsExpandedProperty, value); }
    }

    public static readonly DependencyProperty IsExpandedProperty =
        DependencyProperty.Register("IsExpanded", 
        typeof(bool), 
        typeof(AnimationDecorator),
        new PropertyMetadata(true,IsExpandedChanged));
    
    public static void IsExpandedChanged(DependencyObject d, 
           DependencyPropertyChangedEventArgs e)
    {
        AnimationDecorator expander = d as AnimationDecorator;
        bool expanded = (bool)e.NewValue;
        expander.DoAnimate(expanded);
    }

    /// <summary>
    /// Specify whether to apply animation when IsExpanded is changed.
    /// </summary>

    public DoubleAnimation HeightAnimation
    {
        get { return (DoubleAnimation)GetValue(HeightAnimationProperty); }
        set { SetValue(HeightAnimationProperty, value); }
    }

    public static readonly DependencyProperty HeightAnimationProperty =
        DependencyProperty.Register("HeightAnimation", 
        typeof(DoubleAnimation), 
        typeof(AnimationDecorator), 
        new UIPropertyMetadata(null));

    /// <summary>
    /// Gets or sets the duration for the animation.
    /// </summary>
    public Duration Duration
    {
        get { return (Duration)GetValue(DurationProperty); }
        set { SetValue(DurationProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Duration.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DurationProperty =
        DependencyProperty.Register("Duration", typeof(Duration), 
        typeof(AnimationDecorator), 
        new UIPropertyMetadata(new Duration(new TimeSpan(0,0,0,400))));

    /// <summary>
    /// Perform the animation.
    /// </summary>
    /// <param name="expanded"></param>
    private void DoAnimate(bool expanded)
    {
        if (Child != null)
        {
            if (YOffset > 0) YOffset = 0;

            if (-YOffset > Child.DesiredSize.Height)
                YOffset = -Child.DesiredSize.Height;

            DoubleAnimation animation = HeightAnimation;
            if (animation == null)
            {
                animation = new DoubleAnimation();
                animation.DecelerationRatio = 0.9;
                animation.Duration = Duration;
            }
            animation.From = null;
            animation.To = expanded ? 0 : -Child.DesiredSize.Height;
            this.BeginAnimation(AnimationDecorator.YOffsetProperty, animation);

            if (OpacityAnimation)
            {
                animation.From = null;
                animation.To = expanded ? 1 : 0;
                this.BeginAnimation(Control.OpacityProperty, animation);
            }
        }
        else
        {
            YOffset = int.MinValue;
        }
    }

    protected void SetYOffset(bool expanded)
    {
        YOffset = expanded ? 0 : -Child.DesiredSize.Height;
    }

    /// <summary>
    /// A helper value for the current state while in animation.
    /// </summary>
    internal Double YOffset
    {
        get { return (Double)GetValue(YOffsetProperty); }
        set { SetValue(YOffsetProperty, value); }
    }

    public static readonly DependencyProperty YOffsetProperty =
        DependencyProperty.Register("YOffset", 
        typeof(Double), typeof(AnimationDecorator),
        new FrameworkPropertyMetadata(0.0,
        FrameworkPropertyMetadataOptions.AffectsRender | 
        FrameworkPropertyMetadataOptions.AffectsArrange
        | FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// Measures the child element of a
    /// <see cref="T:System.Windows.Controls.Decorator"/>
    /// to prepare for arranging it during the <see
    /// cref="M:System.Windows.Controls.Decorator.
    ///       ArrangeOverride(System.Windows.Size)"/> pass.
    /// </summary>
    /// <param name="constraint">An upper limit
    /// <see cref="T:System.Windows.Size"/> that should not be exceeded.</param>
    /// <returns>
    /// The target <see cref="T:System.Windows.Size"/> of the element.
    /// </returns>

    protected override Size MeasureOverride(Size constraint)
    {
        if (Child == null) return new Size(0, 0);
            Child.Measure(new Size(Double.PositiveInfinity, 
                                   Double.PositiveInfinity));
        Size size = new Size();
        size.Width = DesiredSize.Width;
        size.Height = Child.DesiredSize.Height;
        Double h = size.Height + YOffset;
        if (h < 0) h = 0;
        size.Height = h;
        if (Child != null) Child.IsEnabled = h > 0;
        return size;
    }

    /// <summary>
    /// Arranges the content of a <see cref="T:System.Windows.Controls.Decorator"/> element.
    /// </summary>
    /// <param name="arrangeSize">The <see cref="T:System.Windows.Size"/>
    /// this element uses to arrange its child content.</param>
    /// <returns>
    /// The <see cref="T:System.Windows.Size"/> that represents the arranged size
    /// of this <see cref="T:System.Windows.Controls.Decorator"/> element and its child.
    /// </returns>

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        if (Child == null) return arrangeSize;
            Size size = new Size();
        size.Width = arrangeSize.Width;
        size.Height = Child.DesiredSize.Height;
        Point p = new Point(0, YOffset);
        Child.Arrange(new Rect(p, size));
        Double h = Child.DesiredSize.Height + YOffset;

        if (h < 0) h = 0;
            size.Height = h;
        return size;
    }
}

}

The OdcExpander itself contains various properties to customize the skin of the control. Actually, I intended to keep some properties internal, such as MouseOverHeaderForeground that specifies the foreground color of the header on mouse-over, or PressedHeaderBackground, etc., but this wouldn't allow you to easily apply custom skins since you would have to completely describe the control template instead of just modifying some properties.

C#
/// <summary>
/// An Expander with animation.
/// </summary>

public class OdcExpander : HeaderedContentControl
{

    static OdcExpander()
    {
        MarginProperty.OverrideMetadata(
            typeof(OdcExpander),
            new FrameworkPropertyMetadata(new Thickness(10, 10, 10, 2)));
            FocusableProperty.OverrideMetadata(typeof(OdcExpander),
            new FrameworkPropertyMetadata(false));

        DefaultStyleKeyProperty.OverrideMetadata(typeof(OdcExpander),
            new FrameworkPropertyMetadata(typeof(OdcExpander)));
    }

    /// <summary>
    /// Gets or sets the custom skin for the control.
    /// </summary>
    public static string Skin { get; set; }

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        ApplySkin();
    }

    public void ApplySkin()
    {
        if (!string.IsNullOrEmpty(Skin))
        {
            Uri uri = new Uri(Skin, UriKind.Absolute);
            ResourceDictionary skin = new ResourceDictionary();
            skin.Source = uri;
            this.Resources = skin;
        }
    }

    public Brush HeaderBorderBrush
    {
        get { return (Brush)GetValue(HeaderBorderBrushProperty); }
        set { SetValue(HeaderBorderBrushProperty, value); }
    }

    public static readonly DependencyProperty HeaderBorderBrushProperty =
        DependencyProperty.Register("HeaderBorderBrush",
        typeof(Brush), typeof(OdcExpander), 
        new UIPropertyMetadata(Brushes.Gray));

    public Brush HeaderBackground
    {
        get { return (Brush)GetValue(HeaderBackgroundProperty); }
        set { SetValue(HeaderBackgroundProperty, value); }
    }

    public static readonly DependencyProperty HeaderBackgroundProperty =
        DependencyProperty.Register("HeaderBackground",
        typeof(Brush), typeof(OdcExpander), 
        new UIPropertyMetadata(Brushes.Silver));

    public bool IsMinimized
    {
        get { return (bool)GetValue(MinimizedProperty); }
        set { SetValue(MinimizedProperty, value); }
    }

    public static readonly DependencyProperty MinimizedProperty =
        DependencyProperty.Register("IsMinimized",
        typeof(bool), typeof(OdcExpander),
        new UIPropertyMetadata(false, IsMinimizedChanged));

    public static void IsMinimizedChanged(DependencyObject d, 
                       DependencyPropertyChangedEventArgs e)
    {
        OdcExpander expander = d as OdcExpander;
        RoutedEventArgs args = new RoutedEventArgs((bool)e.NewValue ? 
                               MinimizedEvent : MaximizedEvent);
        expander.RaiseEvent(args);
    }

    /// <summary>
    /// Gets or sets the ImageSource for the image in the header.
    /// </summary>
    public ImageSource Image
    {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value); }
    }

    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Image", 
        typeof(ImageSource), typeof(OdcExpander), 
        new UIPropertyMetadata(null));

    public bool IsExpanded
    {
        get { return (bool)GetValue(IsExpandedProperty); }
        set { SetValue(IsExpandedProperty, value); }
    }

    public event RoutedEventHandler Expanded
    {
        add { AddHandler(ExpandedEvent, value); }
        remove { RemoveHandler(ExpandedEvent, value); }
    }

    public event RoutedEventHandler Collapsed
    {
        add { AddHandler(CollapsedEvent, value); }
        remove { RemoveHandler(CollapsedEvent, value); }
    }

    public event RoutedEventHandler Minimized
    {
        add { AddHandler(MinimizedEvent, value); }
        remove { RemoveHandler(MinimizedEvent, value); }
    }

    public event RoutedEventHandler Maximized
    {
        add { AddHandler(MaximizedEvent, value); }
        remove { RemoveHandler(MaximizedEvent, value); }
    }

#region dependency properties and routed events definition

    public static readonly DependencyProperty IsExpandedProperty =
        DependencyProperty.Register(
        "IsExpanded",
        typeof(bool),
        typeof(OdcExpander),
        new UIPropertyMetadata(true, IsExpandedChanged));

    public static void IsExpandedChanged(DependencyObject d, 
                    DependencyPropertyChangedEventArgs e)

    {
        OdcExpander expander = d as OdcExpander;
        RoutedEventArgs args = new RoutedEventArgs((bool)e.NewValue ? 
                               ExpandedEvent : CollapsedEvent);
        expander.RaiseEvent(args);
    }

    public static readonly RoutedEvent ExpandedEvent = 
           EventManager.RegisterRoutedEvent(
            "ExpandedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(OdcExpander));

    public static readonly RoutedEvent CollapsedEvent = 
           EventManager.RegisterRoutedEvent(
            "CollapsedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(OdcExpander));

    public static readonly RoutedEvent MinimizedEvent = 
           EventManager.RegisterRoutedEvent(
            "MinimizedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(OdcExpander));

    public static readonly RoutedEvent MaximizedEvent = 
           EventManager.RegisterRoutedEvent(
            "MaximizedEvent",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(OdcExpander));

#endregion

/// <summary>
/// Gets or sets the corner radius for the header.
/// </summary>

public CornerRadius CornerRadius
{
    get { return (CornerRadius)GetValue(CornerRadiusProperty); }
    set { SetValue(CornerRadiusProperty, value); }
}

public static readonly DependencyProperty CornerRadiusProperty =
    DependencyProperty.Register("CornerRadius", typeof(CornerRadius), 
    typeof(OdcExpander), new UIPropertyMetadata(null));

/// <summary>
/// Gets or sets the background color of the header on mouse over.
/// </summary>

public Brush MouseOverHeaderBackground
{
    get { return (Brush)GetValue(MouseOverHeaderBackgroundProperty); }
    set { SetValue(MouseOverHeaderBackgroundProperty, value); }
}

public static readonly DependencyProperty MouseOverHeaderBackgroundProperty =
    DependencyProperty.Register("MouseOverHeaderBackground", 
    typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(null));

/// <summary>
/// Gets whether the PressedBackground is not null.
/// </summary>

public bool HasPressedBackground
{
    get { return (bool)GetValue(HasPressedBackgroundProperty); }
    set { SetValue(HasPressedBackgroundProperty, value); }
}

public static readonly DependencyProperty HasPressedBackgroundProperty =
    DependencyProperty.Register("HasPressedBackground", 
    typeof(bool), typeof(OdcExpander), new UIPropertyMetadata(false));

/// <summary>
/// Gets or sets the background color of the header in pressed mode.
/// </summary>

public Brush PressedHeaderBackground
{
    get { return (Brush)GetValue(PressedHeaderBackgroundProperty); }
    set { SetValue(PressedHeaderBackgroundProperty, value); }
}

public static readonly DependencyProperty PressedHeaderBackgroundProperty =
    DependencyProperty.Register("PressedHeaderBackground", 
    typeof(Brush), typeof(OdcExpander), 
    new UIPropertyMetadata(null, PressedHeaderBackgroundPropertyChangedCallback));

public static void PressedHeaderBackgroundPropertyChangedCallback(DependencyObject d, 
                   DependencyPropertyChangedEventArgs e)
{
    OdcExpander expander = (OdcExpander)d;
    expander.HasPressedBackground = e.NewValue != null;
}

public Thickness HeaderBorderThickness
{
    get { return (Thickness)GetValue(HeaderBorderThicknessProperty); }
    set { SetValue(HeaderBorderThicknessProperty, value); }
}

// Using a DependencyProperty as the backing store
// for HeaderBorderThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderBorderThicknessProperty =
  DependencyProperty.Register("HeaderBorderThickness", 
  typeof(Thickness), typeof(OdcExpander), new UIPropertyMetadata(null));


/// <summary>
/// Gets or sets the foreground color of the header on mouse over.
/// </summary>
public Brush MouseOverHeaderForeground
{
    get { return (Brush)GetValue(MouseOverHeaderForegroundProperty); }
    set { SetValue(MouseOverHeaderForegroundProperty, value); }
}

public static readonly DependencyProperty MouseOverHeaderForegroundProperty =
    DependencyProperty.Register("MouseOverHeaderForeground", 
    typeof(Brush), typeof(OdcExpander), new UIPropertyMetadata(null));

/// <summary>
/// Specifies whether to show a elipse with the expanded/collapsed image.
/// </summary>

public bool ShowEllipse
{
    get { return (bool)GetValue(ShowEllipseProperty); }
    set { SetValue(ShowEllipseProperty, value); }
}

public static readonly DependencyProperty ShowEllipseProperty =
    DependencyProperty.Register("ShowEllipse", typeof(bool), 
    typeof(OdcExpander), new UIPropertyMetadata(false));

}

To simplify the design of the header of the OdcExpander, I wrote a helper control named OdcExpanderHeader, and added some properties for skinning:

C#
/// <summary>
/// A helper class to specify the header of an OdcExpander.
/// </summary>

internal class OdcExpanderHeader : ToggleButton
{
    static OdcExpanderHeader()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(OdcExpanderHeader), 
          new FrameworkPropertyMetadata(typeof(OdcExpanderHeader)));
    }

    /// <summary>
    /// Gets whether the expand geometry is not null.
    /// </summary>
    public bool HasExpandGeometry
    {
        get { return (bool)GetValue(HasExpandGeometryProperty); }
        set { SetValue(HasExpandGeometryProperty, value); }
    }

    public static readonly DependencyProperty HasExpandGeometryProperty =
        DependencyProperty.Register("HasExpandGeometry", typeof(bool), 
        typeof(OdcExpanderHeader), new UIPropertyMetadata(false));


    /// <summary>
    /// Gets or sets the geometry for the collapse symbol.
    /// </summary>
    public Geometry CollapseGeometry
    {
        get { return (Geometry)GetValue(CollapseGeometryProperty); }
        set { SetValue(CollapseGeometryProperty, value); }
    }

    public static readonly DependencyProperty CollapseGeometryProperty =
        DependencyProperty.Register("CollapseGeometry", 
        typeof(Geometry), typeof(OdcExpanderHeader), 
        new UIPropertyMetadata(null));

    public static void CollapseGeometryChangedCallback(DependencyObject d, 
                  DependencyPropertyChangedEventArgs e)
    {
        OdcExpanderHeader eh = d as OdcExpanderHeader;
        eh.HasExpandGeometry = e.NewValue != null;
    }

    /// <summary>
    /// Gets or sets the geometry for the expand symbol.
    /// </summary>
    public Geometry ExpandGeometry
    {
        get { return (Geometry)GetValue(ExpandGeometryProperty); }
        set { SetValue(ExpandGeometryProperty, value); }
    }

    public static readonly DependencyProperty ExpandGeometryProperty =
        DependencyProperty.Register("ExpandGeometry", typeof(Geometry), 
        typeof(OdcExpanderHeader), new UIPropertyMetadata(null, 
                                   CollapseGeometryChangedCallback));

    /// <summary>
    /// Gets or sets the corner radius for the header.
    /// </summary>
    public CornerRadius CornerRadius
    {
        get { return (CornerRadius)GetValue(CornerRadiusProperty); }
        set { SetValue(CornerRadiusProperty, value); }
    }

    // Using a DependencyProperty as the backing store for CornerRadius.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CornerRadiusProperty =
        DependencyProperty.Register("CornerRadius", typeof(CornerRadius), 
        typeof(OdcExpanderHeader), new UIPropertyMetadata(null));

    /// <summary>
    /// Gets or sets whether to display the ellipse
    /// around the collapse/expand symbol.
    /// </summary>
    public bool ShowEllipse
    {
        get { return (bool)GetValue(ShowEllipseProperty); }
        set { SetValue(ShowEllipseProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ShowEllipse.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ShowEllipseProperty =
        DependencyProperty.Register("ShowEllipse", typeof(bool), 
        typeof(OdcExpanderHeader), new UIPropertyMetadata(true));


    /// <summary>
    /// Gets or sets the Image to display on the header.
    /// </summary>
    public ImageSource Image
    {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Image.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Image", typeof(ImageSource), 
        typeof(OdcExpanderHeader), new UIPropertyMetadata(null));

}

Note that I added some properties like HasExpandGeometry. Such properties help to conditionaly define the XAML file, as follows:

XML
<MultiTrigger>
 <MultiTrigger.Conditions>
  <Condition Property="IsChecked" Value="True"/>
  <Condition Property="HasExpandGeometry" Value="True"/>
 </MultiTrigger.Conditions>
 <Setter TargetName="path" Property="Data" 
  Value="{Binding RelativeSource={RelativeSource 
         TemplatedParent},Path=ExpandGeometry}"/>
</MultiTrigger>

In this case, the Multitrigger checks the IsChecked property of the OdcExpanderHelper (which is derived from ToggleButton) together with the HasExpandedGeometry, and only if both values are true, a setter changes the Data of the Path control.

Themes

The OdcExpander supports themes that depend on the current theme of the OS. Therefore, the OdcExpander looks different on Vista and XP.

VistaXP_Metallic.png

The theme is only different when a OdcExpander is inside a ExplorerBar; otherwise, it always uses the same generic style. For instance, the part for the XP metallic snippet looks like:

XML
<Style TargetType="{x:Type local:ExplorerBar}">
 <Setter Property="Background" Value="#FFBDBAD6"/>
 <Setter Property="Focusable" Value="False"/>
 <Setter Property="Template">
 <Setter.Value>
 <ControlTemplate>
 <Border Background="{TemplateBinding Background}">
 <ScrollViewer VerticalScrollBarVisibility="Auto">
  <ItemsPresenter/>
 </ScrollViewer>
 </Border>
 <ControlTemplate.Resources>
  <Style TargetType="{x:Type local:OdcExpander}">
  <Setter Property="HeaderBorderThickness" Value="0"/>
  <Setter Property="HeaderBackground" 
     Value="{StaticResource HeaderBackgroundBrush}"/>
  <Setter Property="Background" Value="{StaticResource ExpanderBg}"/>
  <Setter Property="MouseOverHeaderBackground" 
     Value="{StaticResource HeaderBackgroundBrush}"/>
  <Setter Property="BorderBrush" Value="White"/>
  <Setter Property="MouseOverHeaderForeground" 
     Value="{StaticResource HighlightHeaderTextBrush}"/>
  <Setter Property="CornerRadius" Value="6,6,0,0"/>
  <Setter Property="ShowEllipse" Value="True"/>
</Style>
 </ControlTemplate.Resources>
 </ControlTemplate>
 </Setter.Value>
 </Setter>
</Style>

About Themes

To allow a control to have Windows based themes, you need to specify a custom theme for each Windows theme. Each theme must reside in the Themes folder of the source control as a ResourceDictionary. I implemented the following themes:

  • Generic.xaml (the default style)
  • Classic.xaml (the style for classic Windows themes)
  • Luna.NormalColor.xaml (the style for the blue XP theme)
  • Luna.Metallic.xaml (the style for the silver XP theme)
  • Luna.Homestead.xaml (the style for the olive XP theme)
  • Aero.NormalColor.xaml (the style for the Vista theme)

Each ResourceDictionary is optional. For instance, if you don't specify the Luna.Homestead.xaml, the style falls back to Classic.xaml, if available; otherwise, to Generic.xaml.

But, that's still not all. When you create your first themable control, you'll wonder why it is only using the generic.xaml and never the customized dictionaries. To finally enable theming, you need to modify the AssemblyInfo.cs as follows:

C#
[assembly: ThemeInfo(
 ResourceDictionaryLocation.SourceAssembly,
 //where theme specific resource dictionaries are located
 //(used if a resource is not found in the page, 
 // or application resource dictionaries)

 ResourceDictionaryLocation.SourceAssembly
 //where the generic resource dictionary is located
 //(used if a resource is not found in the page, 
 // app, or any theme specific resource dictionaries)

)]

(For further information, please read the documentation for ThemeInfo.)

Drawback

When you create custom controls, Visual Studio automatically creates a default template for the new control in Generic.xaml. Thus, all styles for all controls of a control library would reside in one XAML. This can become very confusing. But fortunately, it is possible to merge various ResourceDictionarys together. So, I created a folder for each control that contains all the possible styles (generic, Classic, Luna, etc.). Each ResourceDictionary for each theme is now merged with the base ResourceDictionary, as follows:

XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<ResourceDictionary.MergedDictionaries>
<ResourceDictionary 
  Source="pack://application:,,,/Odyssey;Component/Themes/Expander/Luna.HomeStead.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

In Luna.Homestead.xaml, the Style for the Expander is added using MergedDictionaries. So, it is easy to add styles for other controls by adding its ResourceDictionary to the MergedDictionaries block.

History

The ExplorerBar and OdcExpander is part of the Odyssey class library that I'm currently developing for free. It also contains the BreadcrumbBar which was already introduced on CodeProject.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
Germany Germany
MCPD
Enterprise Application Developer 3.5
Windows Developer 3.5
.ASP.NET Developer 3.5
.NET 2.0 Windows Developer
.NET 2.0 Web Developer
.NET 2.0 Enterprise Application Developer


MCTS
.NET 3.5 Windows Forms Applications
.NET 3.5 ASP.NET Applications
.NET 3.5, ADO.NET Application Development
.NET 3.5 WCF
.NET 3.5 WPF
.NET 3.5 WF
Microsoft SQL Server 2008, Database Development
.NET 2.0 Windows Applications
.NET 2.0 Web Applications
.NET 2.0 Distributed Applications
SQL Server 2005
Sharepoint Services 3.0 Application Development
Windows Vista Client Configuration

Comments and Discussions

 
GeneralMy vote of 3 Pin
Athari4-Oct-13 1:27
Athari4-Oct-13 1:27 
Question[My vote of 1] wtf Pin
NilsHult24-Jan-12 1:57
NilsHult24-Jan-12 1:57 
AnswerRe: [My vote of 1] wtf Pin
Thomas Gerber24-Jan-12 7:52
Thomas Gerber24-Jan-12 7:52 
GeneralXBAP Pin
Frankidoze13-Jan-11 6:10
Frankidoze13-Jan-11 6:10 
GeneralRe: XBAP Pin
Frankidoze13-Jan-11 7:08
Frankidoze13-Jan-11 7:08 
GeneralGrate Pin
Mr_Coder14-Jan-10 17:07
Mr_Coder14-Jan-10 17:07 
QuestionUsing only the Animation Control Pin
Ilias Bogordos28-Dec-09 23:35
Ilias Bogordos28-Dec-09 23:35 
QuestionBug fix? Pin
Foxman200012-Aug-09 8:27
Foxman200012-Aug-09 8:27 
GeneralThank you Pin
zhujinlong1984091314-Apr-09 17:45
zhujinlong1984091314-Apr-09 17:45 
QuestionBinding Pin
Steve Fenton12-Feb-09 23:35
Steve Fenton12-Feb-09 23:35 
AnswerRe: Binding Pin
Steve Fenton17-Feb-09 0:23
Steve Fenton17-Feb-09 0:23 
GeneralNicely done Pin
adityakakrania23-Jan-09 6:50
adityakakrania23-Jan-09 6:50 
GeneralSlight problem re: text wrapping Pin
chaiguy133717-Dec-08 19:20
chaiguy133717-Dec-08 19:20 
Hi Thomas, again I'm very thrilled with this but so far have just one minor quibble. I can't seem to get TextBlocks placed inside an Expander to text-wrap. Seems for some reason they extend and "stretch" the content presenter (including the border) beyond its natural size, which looks bad and is obviously not what I want.

I'm going to try fiddling around some more, but just wanted to ask if you have any idea why this is happening and what can be done about it. I think I'll call it a night for tonight and pick it up again tomorrow. Smile | :)

chai

Sad but true: 4/3 of Americans have difficulty with simple fractions.

There are 10 types of people in this world: those who understand binary and those who don't.

{o,o}.oO( Check out my blog! )
|)””’)          http://pihole.org/
-”-”-

GeneralRe: Slight problem re: text wrapping Pin
chaiguy133718-Dec-08 7:51
chaiguy133718-Dec-08 7:51 
GeneralRe: Slight problem re: text wrapping Pin
Thomas Gerber14-Jan-09 12:06
Thomas Gerber14-Jan-09 12:06 
GeneralRe: Slight problem re: text wrapping Pin
AngulaDev11-Feb-10 3:23
AngulaDev11-Feb-10 3:23 
GeneralGreat work! Pin
chaiguy133717-Dec-08 6:06
chaiguy133717-Dec-08 6:06 
GeneralExcellent work! Pin
John Schroedl8-Oct-08 9:03
professionalJohn Schroedl8-Oct-08 9:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.