Freigeben über


Command helper classes for Silverlight & WPF

Here's a couple classes I've found helpful when writing WPF & Silverlight applications, which I've named simply Command and CommandHelper.  Sometimes, you just want to do simple commanding stuff, and you don't need the overhead of RoutedCommand.  (Also, Silverlight doesn't have RoutedCommand)  The Command class below gives you a quick & easy way to define commands and ways to invoke them: keyboard shortcuts, context menus, toolbars, etc.  Sample usage:

            command = new Command();
            command.Text = "Zoom";
            command.Key = Key.Z;
            command.ModifierKeys = ModifierKeys.Control;
            command.Button = zoomButton;
            command.Execute += delegate() {
                imageDisplay.Zoom = !imageDisplay.Zoom;
            };
            commands.AddCommand(command); // call CommandHelper.AddCommand

As a bonus, by avoiding WPF KeyBinding, you can tie your commands to keystrokes that WPF's KeyBinding wouldn't let you, such as "A" w/o ctrl or alt modifiers.

Here's the complete code:

    public class Command : ICommand
    {
        public event SimpleDelegate Execute;
        //public event CancelEventHandler CanExecute;

        void ICommand.Execute(object parameter)
        {
            if (Execute != null)
                Execute();
        }

        bool ICommand.CanExecute(object parameter)
        {
            // not necessary for this application, and CancelEventArgs doesn't exist on Silverlight
            //CancelEventArgs args = new CancelEventArgs(false);
            //if (CanExecute != null)
            //    CanExecute(this, args);
            //return !args.Cancel;
            return true;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add { }
            remove { }
        }

        public Key Key = Key.None;
        public string DisplayKey;
        public ModifierKeys ModifierKeys = ModifierKeys.None;
        public string Text = "";
        public bool HasMenuItem = true;
        public Button Button = null; // hooks up the command to the button
    }

    public class CommandHelper
    {
        private UIElement owner;
        private List<Command> commands = new List<Command>();

        public CommandHelper(UIElement owner)
        {
            this.owner = owner;
            owner.KeyDown += new KeyEventHandler(keyDown);
        }

        private void keyDown(object sender, KeyEventArgs e)
        {
            foreach (Command command in commands) {
                // Intentionally ignore modifier keys
                bool shiftKeyMatches = (command.ModifierKeys & ModifierKeys.Shift) == (Keyboard.Modifiers & ModifierKeys.Shift);
                if (command.Key == e.Key && shiftKeyMatches) {
                    (command as ICommand).Execute(null);
                }
            }
        }

 

#if WPF
        public void AddBinding(Command command, RoutedCommand applicationCommand)
        {
            CommandBinding binding = new CommandBinding(applicationCommand);
            binding.Executed += delegate(object sender, ExecutedRoutedEventArgs e)
            {
                ((ICommand)command).Execute(null);
            };
            owner.CommandBindings.Add(binding);
        }

        public ContextMenu contextmenu;
#endif

        public void AddMenuSeparator()
        {
#if WPF
            var item = new Separator();
            contextmenu.Items.Add(item);
#endif
        }

        public void AddCommand(Command command)
        {
            commands.Add(command);

            // KeyBinding insists that ModifierKeys != 0 for alphabetic keys,
            // so we have to roll our own
            //this.CommandBindings.Add(new CommandBinding(command));
            //KeyGesture gesture = new KeyGesture(command.Key, command.ModifierKeys);
            //this.InputBindings.Add(new KeyBinding(command, gesture));

#if WPF
            if (command.HasMenuItem) {
                MenuItem item = new MenuItem();
                string text = command.Text + ShortcutText(command);
                item.Header = text;
                item.Command = command;
                contextmenu.Items.Add(item);
            }
#endif

            if (command.Button != null) {
                string text = command.Text + ShortcutText(command);
                ToolTip tooltip = new ToolTip();
                tooltip.Content = text;
                tooltip.Background = (Brush)Application.Current.Resources["menuBackground"];
                tooltip.Foreground = (Brush)Application.Current.Resources["menuForeground"];
                tooltip.BorderBrush = (Brush)Application.Current.Resources["shotclockBrush"];
                command.Button.Click += (object sender, RoutedEventArgs e) => {
                    (command as ICommand).Execute(null);
                };
#if WPF
                command.Button.ToolTip = tooltip;
                //command.Button.Command = command;
#endif
            }
        }

        private static string ShortcutText(Command command)
        {
            string text = "";
            string keyText = null;
            if (command.DisplayKey != null)
                keyText = command.DisplayKey;
            else if (command.Key != Key.None) {
                keyText = command.Key.ToString();
                if ((command.ModifierKeys & ModifierKeys.Shift) != 0)
                    keyText = "shift+" + keyText;
            }

            if (keyText != null)
                text += " (" + keyText + ")";
            return text;
        }
    }

Some assembly required:

  • Inside AddCommand when creating tool tips for toolbar buttons, I put some app-specific styling logic...
  • I punted on supporting CanExecute.  It's trivial to get working in WPF, just uncomment the code above.  Silverlight is nontrivial, hard part is deciding when to call it for the toolbar case -- WPF essentially polls on a timer with some heuristics to minimize perf cost, you'll need to figure out what heuristics work for your Silverlight app.

Enjoy!

Comments

  • Anonymous
    January 14, 2009
    Very cool, thanks for sharing this Nick. Question I've wondered before: would it be better to use the !SILVERLIGHT definition check instead of WPF? Although Visual Studio Silverlight projects define SILVERLIGHT, I didn't think that WPF projects did anything similar.

  • Anonymous
    January 15, 2009
    Very nice Nick, thanks for the tip ... just one quick question, it may be a stupid one but, does this code goes as a regular .net library pointing to the full .net framework ? how do the integration with wpf and sl2 works ? Thank you

  • Anonymous
    January 16, 2009
    Jeff -- good catch, !SILVERLIGHT is more portable.  In my project I had added the WPF directive. leo -- it's not part of any Microsoft product, just sample code -- cut & paste into your project and have fun. I should have included the using clauses: using System; using System.Collections.Generic; using System.Text; using System.Windows.Input; using System.Windows; using System.ComponentModel; using System.Windows.Controls; using System.Windows.Media;

  • Anonymous
    January 18, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    February 07, 2009
    Nice work! Can you post the whole code example? What is the SimpleDelegate ?

  • Anonymous
    February 10, 2009
    Sorry about that, here's SimpleDelegate:    public delegate void SimpleDelegate(); Told you it was simple.  <g>