Click here to Skip to main content
15,878,852 members
Articles / Desktop Programming / WPF

XPlorerBar: A WPF Windows XP Style Explorer Bar Control

Rate me:
Please Sign up or sign in to vote.
4.95/5 (168 votes)
9 Dec 2008CPOL11 min read 340.5K   9.3K   408  
A fully customizable WPF implementation of the left side pane that was introduced in Windows XP's Explorer.
#region [       Copyright © 2008, Zona-Tools, all rights reserved.       ]
/*
 * 
    This source code is licensed under the Code Project Open License (CPOL).
    Check out http://www.codeproject.com/info/cpol10.aspx for further details.
 * 
*/
#endregion


#region [       Using namespaces       ]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

#endregion


namespace ZonaTools.XPlorerBar
{
    #region [       Theme definition structure       ]

    /// <summary>
    /// Represents a theme with its name and the path to its resource dictionary.
    /// </summary>
    public struct Theme
    {
        /// <summary>
        /// Name of the theme.
        /// </summary>
        public string Name;
        /// <summary>
        /// Path of the theme resource dictionary.
        /// </summary>
        public string DictionaryPath;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="name">Name of the theme.</param>
        /// <param name="dictionaryPath">Path of the theme resource dictionary.</param>
        public Theme(string name, string dictionaryPath)
        {
            Name = name;
            DictionaryPath = dictionaryPath;
        }
    }

    #endregion


    #region [       Theme names enumeration       ]

    /// <summary>
    /// Specifies the names of the most common themes.
    /// </summary>
    public enum Themes
    {
        /// <summary>
        /// Name used to unload the themes of an element.
        /// </summary>
        Default,
        /// <summary>
        /// Name of the Aero.NormalColor theme.
        /// </summary>
        Aero,
        /// <summary>
        /// Name of the Classic theme.
        /// </summary>
        Classic,
        /// <summary>
        /// Name of the Luna.NormalColor theme.
        /// </summary>
        LunaBlue,
        /// <summary>
        /// Name of the Luna.Homestead theme.
        /// </summary>
        LunaOliveGreen,
        /// <summary>
        /// Name of the Luna.Metallic theme.
        /// </summary>
        LunaSilver,
        /// <summary>
        /// Name of the Royale.NormalColor theme.
        /// </summary>
        Royale,
        /// <summary>
        /// Name of the Zune.NormalColor theme.
        /// </summary>
        Zune,
        /// <summary>
        /// Name of the Blend theme.
        /// </summary>
        Blend
    };
    
    #endregion


    /// <summary>
    /// Manages the themes used by any <c>FrameworkElement</c>.
    /// </summary>
    public static class ThemeManager
    {
        #region [       String definitions       ]

        private const string Lang_Caption =
            "Theme Management";
        private const string Lang_ChangeThemeToPb =
            "Unable to change the current theme because the '{0}' theme is not registered for the '{1}' objects.";
        private const string Lang_RegisterThemePb =
            "Unable to register the '{0}' theme for the '{1}' objects.";

        #endregion


        #region [       Fields       ]

        /// <summary>
        /// Represents the collection of all the registered themes.
        /// </summary>
        /// <remarks>
        /// The key of this dictionary represents the name used to register a theme.
        /// </remarks>
        private static Dictionary<string, ResourceDictionary>
            _registeredDictionaries = new Dictionary<string, ResourceDictionary>();

        #endregion


        #region [       Dependency properties       ]

        #region Theme attached property

        //===========================================================================
        /// <summary>
        /// Identifies the <c>Theme</c> attached property.
        /// </summary>
        /// <remarks>The default value is <c>Default</c>.</remarks>
        //===========================================================================
        public static readonly DependencyProperty ThemeProperty =
            DependencyProperty.RegisterAttached("Theme", 
            typeof(string), typeof(ThemeManager),
                new FrameworkPropertyMetadata(Themes.Default.ToString(),
                    FrameworkPropertyMetadataOptions.None,
                    new PropertyChangedCallback(OnThemeChanged)));

        /// <summary>
        /// Gets the value of the <c>Theme</c> attached property for a 
        /// specified <c>FrameworkElement</c>.
        /// </summary>
        [Category(XPlorerBar.CATEGORYNAME), AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
        public static string GetTheme(DependencyObject d)
        {
            if (d == null)
                throw new ArgumentNullException("d");
            return (string)d.GetValue(ThemeProperty);
        }

        /// <summary>
        /// Sets the value of the <c>Theme</c> attached property to a 
        /// specified <c>FrameworkElement</c>.
        /// </summary>
        public static void SetTheme(DependencyObject d, string value)
        {
            if (d == null)
                throw new ArgumentNullException("d");
            d.SetValue(ThemeProperty, value);
        }

        /// <summary>
        /// Invoked whenever the <c>Theme</c> attached property value has 
        /// been updated.
        /// </summary>
        /// <param name="d">The <c>DependencyObject</c> on which the property has 
        /// changed value.</param>
        /// <param name="e">Event data that is issued by any event that tracks changes 
        /// to the effective value of this property.</param>
        private static void OnThemeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Gets the element on which apply the new theme
            FrameworkElement element = d as FrameworkElement;

            //Makes sure the arguments are valid
            if (element == null)
                throw new ArgumentNullException("d");
            if (e == null)
                throw new ArgumentNullException("e");

            //-------------------------------------------------------------
            //Removes the current theme dictionary from element's resources
            //-------------------------------------------------------------

            //Gets the name of the current theme
            string curThemeName = e.OldValue as string;

            //If the current theme is the default theme, there's nothing to remove
            if (!curThemeName.Equals(Themes.Default.ToString()))
            {
                //Gets the registered name of the current theme
                string curRegisteredThemeName = 
                    GetRegistrationName(curThemeName, element.GetType());

                //If the current theme is registered
                if (_registeredDictionaries.ContainsKey(curRegisteredThemeName))
                {
                    //Gets the resource dictionary of the current theme
                    ResourceDictionary curThemeDictionary =
                        _registeredDictionaries[curRegisteredThemeName];
                    //and removes it
                    element.Resources.MergedDictionaries.Remove(curThemeDictionary);
                }
            }

            //----------------------------------------------------
            //Adds the new theme dictionary to element's resources
            //----------------------------------------------------

            //Gets the name of the new theme
            string newThemeName = e.NewValue as string;

            //If the new theme is the default theme, there's nothing to add
            if (!newThemeName.Equals(Themes.Default.ToString()))
            {
                //Gets the registered name of the new theme
                string newRegisteredThemeName =
                    GetRegistrationName(newThemeName, element.GetType());

                //If the new theme is not registered
                if (!_registeredDictionaries.ContainsKey(newRegisteredThemeName))
                {
                    //Displays an error message
                    MessageBox.Show(
                        string.Format(Lang_ChangeThemeToPb, newThemeName, element.GetType().ToString()),
                        Lang_Caption, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
                else
                {
                    //Gets the resource dictionary of the new theme
                    ResourceDictionary newThemeDictionary =
                        _registeredDictionaries[newRegisteredThemeName];
                    //and applies it to the source element
                    element.Resources.MergedDictionaries.Add(newThemeDictionary);
                }
            }
        }

        #endregion

        #endregion


        #region [       Registers a new theme       ]

        //===========================================================================
        /// <summary>
        /// Registers a new theme with a specified name and a specified <c>Uri</c>.
        /// </summary>
        /// <param name="theme">Theme to register.</param>
        /// <param name="ownerType">The owner type that is registering the theme.
        /// </param>
        //===========================================================================
        public static void RegisterTheme(Theme theme, Type ownerType)
        {
            //Makes sure the arguments are valid
            if ((theme.Name == null) || (theme.DictionaryPath == null))
                throw new ArgumentNullException("theme");
            if (ownerType == null)
                throw new ArgumentNullException("ownerType");

            //Gets the registration name
            string registrationName = 
                GetRegistrationName(theme.Name, ownerType);

            //Checks that the new theme is not the default theme and 
            //that it is not already registered
            if (theme.Name.Equals(Themes.Default.ToString()) ||
                _registeredDictionaries.ContainsKey(registrationName))
                return;

            try
            {
                //Creates the Uri of the specified theme
                Uri themeUri = new Uri(theme.DictionaryPath, UriKind.Relative);

                //Registers the new theme
                _registeredDictionaries[registrationName] =
                    Application.LoadComponent(themeUri) as ResourceDictionary;
            }
            catch
            {
                //Dislays an error message
                MessageBox.Show(
                    string.Format(Lang_RegisterThemePb, theme.Name, ownerType.ToString()),
                    Lang_Caption, MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }


        //===========================================================================
        /// <summary>
        /// Gets the name used for the registration of the specified theme.
        /// </summary>
        /// <param name="themeName">Name of the theme to register.</param>
        /// <param name="ownerType">The owner type that is registering the theme.
        /// </param>
        /// <returns>Name used for the registration of the specified theme.</returns>
        //===========================================================================
        private static string GetRegistrationName(string themeName, Type ownerType)
        {
            //Sets the registration name (Type name;theme name)
            string registrationName =
                string.Format("{0};{1}", ownerType.ToString(), themeName);

            return registrationName;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader
France France
I have been developing and managing projects for real-time embedded softwares for eight years. Then, I moved from Paris to the south of France and began to lead a team who was developping Java applications.

My main occupation right now is to continue my journey in the WPF world.

You can check out my blog here. [^]

Comments and Discussions