Your Privacy Matters: We use our own and third-party cookies to improve your experience on our website. By continuing to use the website we understand that you accept their use. Cookie Policy
1090
Dynamic Menu In Ribbon/Changing Themes
posted

Hey,

 I am trying to set up a menu in my ribbon that allows my users to dynamically choose a theme.  I didn't have any luck finding anything built in to do this (does something like this exist?) so I tried something on my own.  Attached is a sample project

 Basically I used the ThemeManager class to get a list of the themes, created model objects from the themes, put them on a view model, and bound the ItemsSource of the application menu to ViewModel.Themes (I put the button tools directly in the application menu in my sample code so that it's quicker to get to -- once its working I would want it in a menu item in the application menu).  I set up a DataTemplate to create button tools for each of my theme model objects.  The button tools are bound to a ChangeThemeCommand, passing the theme model as the argument to the command.

 My questions are:

1.) Is the way that I set up the DataTemplate/ItemsSource binding to fill the menu and bind to my command correct or is there some sort of bug with command binding?

The command binding sort of works, but is extremely flakey.  If I click the Fall theme one time, it does nothing.  If I keep clicking it, it eventually works.  It's not hitting my command's CanExecute methods so it feels like the command isn't bound for the first few clicks? 

Being able to set up dynamic buttons bound to commands is particularly important to me because I know of a few other places where I'll need to do this.

2.) Is the way that I change themes valid (in the ChangeThemeCommand class)?

I was guessing when I set up that chunk of code.  Basically, I want as many of the controls as possible to take on the theme that the user chooses and the rest to have some sort of default theme (ie if the new theme doesn't include them).

 

Thanks

ThemeSample.rar
Parents
  • 54937
    Verified Answer
    Offline posted

    No, you won't be able to use this specific approach. The binding isn't flakey. What is happening is that the ButtonTool from your datatemplate is becoming the content in the content presenter within the menu item so it appears to work as long as you click on the text of the menu item (which is where the contentpresenter is within the menu item). If you want to take this kind of approach then you will need to expose a collection of commands and bind that to the itemssource. The menu item uses that command as the command for itself. So you will need to create a separate command instance for each theme - probably just add a ThemeModel property to it - and bind to the collection of those. Since you will have different instances of the command, you'll probably need to statically share the theme resource dictionary you added.

    BTW, be careful about your ICommand implementation. Just because you're not using the CanExecuteChanged doesn't mean something won't be hooked into it so if you keep that command around you could end up rooting elements that were using that command. If you don't need it then you could explicitly implement empty add{} remove{} for the event or follow what routed command does which is to add the listener to the CommandManager.RequerySuggested. The former is probably ok since the state of the command is constant.

    Here's a modified version of the command class.

    [TypeConverter(typeof(ThemeCommandConverter))]
    public class ThemeCommand : ICommand
    {
        private ThemeModel _themeModel;
        private static ResourceDictionary _currentThemeResourceDictionary;
     
        internal ThemeCommand(ThemeModel model)
        {
            if (model == null)
                throw new ArgumentNullException("model");
     
            this._themeModel = model;
        }
     
        #region ICommand Members
     
        bool ICommand.CanExecute(object parameter)
        {
            return true;
        }
     
        event System.EventHandler ICommand.CanExecuteChanged
        {
            add { ; }
            remove { ; }
        }
     
        void ICommand.Execute(object parameter)
        {
            lock (typeof(ThemeCommand))
            {
                string themeName = this._themeModel.ThemeName;
     
                if (_currentThemeResourceDictionary != null)
                {
                    Application.Current.Resources.MergedDictionaries.Remove(_currentThemeResourceDictionary);
                }
     
                ResourceDictionary resourceDictionary = ThemeManager.GetResourceSet(themeName, ThemeManager.AllGroupingsLiteral);
     
                Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
     
                ThemeManager.CurrentTheme = themeName;
       
                _currentThemeResourceDictionary = resourceDictionary;
            }
        }
     
        #endregion
     
        public override string ToString()
        {
            return this._themeModel.ThemeName;
        }
    }
     
    internal class ThemeCommandConverter : TypeConverter
    {
        private static readonly TypeConverter CommandConverter = new CommandConverter();
     
        public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
        {
            return CommandConverter.CanConvertFrom(context, sourceType);
        }
     
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            return CommandConverter.ConvertFrom(context, culture, value);
        }
     
        public override bool CanConvertTo(ITypeDescriptorContext context, System.Type destinationType)
        {
            return CommandConverter.CanConvertTo(context, destinationType);
        }
     
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, System.Type destinationType)
        {
            if (value is ThemeCommand && destinationType == typeof(string))
                return value.ToString();
     
            return CommandConverter.ConvertTo(context, culture, value, destinationType);
        }
    }
     

     

Reply Children