locked
Global RoutedCommand RRS feed

  • Question

  • How can I have a global RoutedCommand? By global I mean that no matter which Page it's Executed from, it will be recieved in all pages.
    Friday, February 3, 2006 5:04 AM

Answers

  • I think it's an interesting idea.  We talked about letting people create customized command routing -- you could almost kind of sort of do it with BuildRouteCore.  But after  we talked about some more, it became apparent it's not something we can do in V1, there's a fair amount of surface area to test and commanding is actually fairly sensitive security-wise...   So thank you for the suggestion, we'll keep it in mind for V2.  Thanks.
    Wednesday, February 8, 2006 1:37 AM

All replies

  • I'm not aware of any way to do this since commands bubble up, not sideways.

    Can you describe what it is you want to do? With some more details, maybe someone will be able to recommend a good way to do it.

    Cheers,
    Drew

    Friday, February 3, 2006 3:39 PM
  • Sure, thanks for helping!

     

    We are working on a Media Center style application. It will of course play music and videos, but it will also display photos and have a task pad for launching applications.

     

    To support multiple tasks, we chose to build the application using Navigation. Each task (playing video, launching apps, etc) is presented to the user as a navigation Page. We allow the use of the journal for Back / Forward navigation but we also have a Start page that lets you jump directly to a particular task page. Some pages, like the Media page, are treated as singletons. They exist throughout the lifetime of the application so that, for example, music playback can continue in the background no matter what task you’re performing. When you return to these pages it’s as if you never left.

     

    Anyway, we really want to make it easy to build new pages and add new functionality to this app. One of the things we thought of early on is that pages other then the Media page might want to be able to control media playback. While watching a slideshow on the Photo page, for example, you might want to skip to the next track in your playlist without having to go all the way to the Media page and back. Common panels that could slide out over the page are one option, but another page might want to place the ‘next track’ button directly in the page layout.

     

    We came up with the idea of making our own button classes (or at the very least or own button styles) for each type of media action. PlayButton, PauseButton, NextTrackButton and so on. RoutedCommands seem like a really great way of implementing this functionality because they can easily be associated with a button in a style through a setter of the Command property. Commands also support keyboard bindings, which is another thing we’d like to add.

     

    We threw the buttons and styles together in a very short time and when we placed them on the Media page where the MediaElement is displayed, everything worked great. But the moment we started adding them to other pages (like the Photo page) they stopped working. We could add a CommandBinding to the Photo page and receive it just fine, but our Media page living on in the background never gets notified.

     

    I know this likely has to do with the routing of events and bubbling. I’ve read a great deal about routed events in the last 24 hours and I know that you can also specify that an event should be tunneled. Tunneled events start at the root of the tree and work their way down. You can also subscribe to an event in a way that you will be notified even if the event is marked as handled elsewhere in the tree.

     

    I found when you declare a CommandBinding in XAML you can handle Executed or PreviewExecuted, but PreviewExecuted never seems to be raised. As I said earlier, Executed is only raised in the scope of the active page. This may be the case even if the event is tunneled.

     

    I even went so far as trying to create my own BroadcastCommand that implements ICommand. My thoughts were that I would make sure PreviewExecuted gets fired and not stop even if someone marks it as handled. Unfortunately, while trying to do this I ran into a WinFX class that had only internal constructors. I don’t remember what that class was now and that code is at home, but essentially I hit a dead end. I can talk more about that later, but like I said: tunneled events may still only have page scope.

     

    Any suggestions are certainly welcome. I believe this is a proper use for commands, but commands that have application-wide scope. Thanks for your ideas.

     

    Jared Bienz

    Friday, February 3, 2006 5:47 PM
  • Jared,

    Sounds to me like you're doing some exciting stuff! Everything you described seems to be perfectly valid design, but obviously it does present a challenge. While commands are not the same as events, they do follow the same routing strategy. I'm wasn't sure why you're weren't getting PreviewExecuted for your commands, but I just tested it and noticed that if I didn't hook up the Executed event of the CommandBinding, the PreviewExecuted event wouldn't fire either. That seems a little weird to me, maybe a bug...?

    Anyway, I was trying to think of a way that you could essentially transplant the RoutedCommand when it bubbled up to the top of PageA to PageB some how. You can technically call Execute on any RoutedCommand instance to cause it to "fire". If you put the requirement that all such commands need to be handled at the Page level for your application you can make this work because you can take every command that ever bubbles up through one page and transfer it to the other pages by looping through all the pages and calling command.Execute(args.Parameter, page). That's pretty much the only way I can think to do it. You *could* probably create some kind of architecture within your application where you create a base class for all application pages that derives from Page and adds like... a collection of RoutedCommands that the page wants to hear about. That way you're not re-broadcasting all commands to all pages all the time.

    HTH,
    Drew

    Friday, February 3, 2006 7:13 PM
  • Drew,
    I noticed that without a binding to Executed, the button was grayed out and couldn't be clicked. However, even after I added a binding to Executed, PreviewExecuted was still not fired. The handler for Executed was hit, but the handler for PreviewExecuted was not.

    Your idea for event propagation is very interesting. What shies me away from it, though, is that each page must somehow keep track of the others and notify them. I could handle this in a base page class, but I would much rather handle it at an application level. In fact, I was really hoping there would be a way to add CommandBindings to the Application. Application.CommandBindings would have worked pretty well actually, but unfortunately we can only add bindings to a Page or Window.

    Now, if we decide to do page to page command propagation, we must somehow obtain a listing of all page instances. I immediately thought to look for a Pages property on NavigationWindow, but it doesn’t exist. The more I thought about it, that’s probably valid because the only pages NavigationWindow knows about are the ones currently in the journal. Like I said in my earlier post, we have some pages that are considered singletons and may be instantiated without actually being in the journal. I suppose we are going to need some kind of page manager to deal with this anyway, so the collection of all loaded pages could be provided by that manager.

    I would really like to implement a custom command object called BroadcastCommand that could be used in XAML and perform this event propagation. BroadcastCommand would implement ICommand, and Execute would create a private RoutedCommand instance with the same parameter and in turn execute it on each page.

    Do you see any pitfalls with this approach? Also, I’d really like to hear from someone on the Avalon team on this one. Are they planning support for something like a BroadcastCommand? Is there a better way of doing what we’re doing?

    Thanks again for your ideas Drew. I truly believe that feedback is paramount for early adopters.

    Regards,

    Jared

    Friday, February 3, 2006 9:11 PM
  • Oh, and your idea bout having a list of events the page cares about is good too.

    This reminds me very much of Pub/Sub events in the Component UI Block. If you are not familliar, a component can expose an event and decorate it with an attribute that makes it a CAB event. The attribute includes a string that specifies it's global topic name. Other modules throught the application can say they want to be notified whenever the topic is fired and it all just works. They don't have to know anything about the object that is raising the event; they only need to know the topic name.

    I think this form of notification is the only thing that's missing from the WPF Command framework.

    For more information about CAB Pub / Sub, please see:

    http://blogs.msdn.com/edjez/archive/2005/04/20/CABEventBroker101.aspx

    Jared

    Friday, February 3, 2006 9:53 PM
  • Your idea for event propagation is very interesting. What shies me away from it, though, is that each page must somehow keep track of the others and notify them. I could handle this in a base page class, but I would much rather handle it at an application level.

    Don't you have only one NavigationWindow for your entire application? Why not handle it there? I was kinda taking it for granted that's how your application worked, if not then yeah it's not going to be as easy.

    I could handle this in a base page class, but I would much rather handle it at an application level. In fact, I was really hoping there would be a way to add CommandBindings to the Application. Application.CommandBindings would have worked pretty well actually, but unfortunately we can only add bindings to a Page or Window.

    A long time ago I made the suggestion that WPF should route events and commands up to the application level for exactly these types of scenarios. Unfortunately that hasn't happened yet, but now that you've provided a more concrete scenario maybe they'll see the value in it.

    I would really like to implement a custom command object called BroadcastCommand that could be used in XAML and perform this event propagation. BroadcastCommand would implement ICommand, and Execute would create a private RoutedCommand instance with the same parameter and in turn execute it on each page.

    Do you see any pitfalls with this approach? Also, I’d really like to hear from someone on the Avalon team on this one. Are they planning support for something like a BroadcastCommand? Is there a better way of doing what we’re doing?

    No I don't see anything wrong with it, but it seems like you'd have to do a lot of the same work and more. Not to mention it requires your developers to change the typical way they work with commands. It just seems like handling everything that bubbles up to the NavigationWindow level will keep everything feeling more "natural", but that's just my opinion. :)

    Cheers,
    Drew

    Friday, February 3, 2006 10:22 PM
  • You are absolutely right that I have one NavigationWindow. I honestly don't know why I didn't consider handling it there. I'll have to put some more thought into that one.

    I agree that roughly the same work would go into a base page as would building a BroadcastCommand. And being able to handle it at the NavigationWindow may nullify all of this, but what do you feel would make things odd for the developers?

    The way I conceived it is to define broadcast messages as resources at the application level like:

    <Application.Resources>

      <BroadcastCommand x:Key="MediaPlay" />

    </Application.Resources>

    The style for the PlayButton would then simply be something like:

    <Style x:Key="PlayButton">

      <Setter Property="Button.Command" Value="{StaticResource MediaPlay}" />

    </Style>

    Placing the play button into a page would be as simple as:

    <mbc:PlayButton>Play</mbc:PlayButton>

    And of course, handling the command in a page would be identical to handling a regular RoutedCommand:

    <Page.CommandBindings>

      <CommandBinding Command="{StaticResource MediaPlay}" Executed="Command_MediaPlay" />

    </Page.CommandBindings>

    Do you feel there is something lacking in that design?

    Friday, February 3, 2006 11:07 PM
  • I just remembered what I hit last night trying to develop this BroadcastCommand.

     

    The problem is that CommandBinding binds a page to an event handler of type ExecutedRoutedEventHandler. ExecutedRoutedEventHandler takes an instance of ExecutedRoutedEventArgs. ExecutedRoutedEventArgs does not have a publicly defined constructor.

     

    If I follow my design above and have BroadcastCommand internally create a RoutedCommand and execute it on each window, the Command property of the ExecutedRoutedEventArgs will be the internal RoutedCommand and not the true BroadcastCommand. Since there is no public constructor for ExecutedRoutedEventArgs, I cannot perform my own logic to pass the BroadcastCommand in the args.

    Friday, February 3, 2006 11:25 PM
  • If I follow my design above and have BroadcastCommand internally create a RoutedCommand and execute it on each window, the Command property of the ExecutedRoutedEventArgs will be the internal RoutedCommand and not the true BroadcastCommand. Since there is no public constructor for ExecutedRoutedEventArgs, I cannot perform my own logic to pass the BroadcastCommand in the args.

     

    I'm not sure I understand... maybe I'm just missing something. Why do you feel you need to create a new RoutedCommand "internally"? Why wouldn't you just Execute the BroadcastCommand that bubbled up to the Window/Page? They instance is completely re-usable.

     

    That aside, the only hole that I see in BroadcastCommand design is that if there are existing commands not created within the scope of your application that you want to be broadcasted there's no way to do it. For example, any of the ApplicationCommands or perhaps some third party commands. Since they're not BroadcastCommands, how would you ever work them into this architecture?

     

    Cheers,

    Drew

    Saturday, February 4, 2006 12:03 AM
  • That's a very good point about existing ApplicationCommands or other third-party commands based on RoutedCommand not being able to be broadcasted. That's why I really think Microsoft should consider addressing this need, or offer alternative suggestions.

    Thus far, however, you're the only one that has offered insight into this. I'm not sure anyone from the WPF team has seen this thread. What steps can we take to bring this to light with the team?

    Over the last two evenings I delved deeply into the command system trying to figure out a way to make this work. I took notes and I'm going to include them here to hopefully help people understand the shortcommings we've found with extending the command system. These notes may be helpful to the WPF team.

    Thanks again for your time and ideas Drew. It's really helping us think about the direction we're headed.

    Jared

     

    All commands are processed through the CommandManager. CommandManager has methods like AddExecutedHandler, AddPreviewExecutedHandler and so on. These methods all expect a handler that uses the ExecutedRoutedEventHandler delegate. The ExecutedRoutedEventHandler delegate requires the use of ExecutedRoutedEventArgs.

     

    Furthermore, the CommandBinding class (which is the only way to bind to a command in XAML) internally processes commands in a method called OnExecuted. OnExecuted also takes a parameter of type ExecutedRoutedEventArgs.

     

    Essentially, the entire WPF command system is tied to the use of ExecutedRoutedEventArgs. The problem is that ExecutedRoutedEventArgs has no public or protected constructor and therefore cannot be created by anyone outside of the core WPF.

     

     

    For example, I tried creating my BroadcastCommand class and having it implement ICommand. When I realized that I could not create an instance of ExecutedRoutedEventArgs, I defined my own PreviewExecuted and Executed RoutedEvents that used my own ExecutedBroadcastEventHandler and ExecutedBroadcastEventArgs.

     

    I was able to define a broadcast command, tie it to a button and execute it in XAML just like a regular routed command. Unfortunately, though, the command was never processed by any of the command bindings even though I could see the command was being sent to the window and the page.

     

    That’s when I noticed that CommandBinding was built to process only ExecutedRoutedEventArgs, for which there is no public constructor. What’s more, I also noticed that CommandBinding internally filters out any events that are not CommandManager.ExecutedEvent or CommandManager.PreviewExecutedEvent. And again, CommandManager.ExecutedEvent and CommandManager.PreviewExecutedEvent require the use of ExecutedRoutedEventArgs.

     

     

    Anyway, even if you haven’t been able to follow me through this entire process, what you should walk away with is this:

     

    1. The WPF command system requires the use of ExecutedRoutedEventArgs.
    2. ExecutedRoutedEventArgs has no public constructor and cannot be created outside of WPF itself.
    3. Even though there is an ICommand interface, the rest of the system is so closely tied to RoutedCommand and ExecutedRoutedEventArgs that creating your own type of command object really isn’t supported.
    Sunday, February 5, 2006 9:09 PM
  • I think it's an interesting idea.  We talked about letting people create customized command routing -- you could almost kind of sort of do it with BuildRouteCore.  But after  we talked about some more, it became apparent it's not something we can do in V1, there's a fair amount of surface area to test and commanding is actually fairly sensitive security-wise...   So thank you for the suggestion, we'll keep it in mind for V2.  Thanks.
    Wednesday, February 8, 2006 1:37 AM
  • Hi Jared,

     

    This may be a bit of a glib response, but have you tried using the CommandManager.RegisterClassCommandBinding method to acheive what you are looking for?  You could presumably use it to register a binding for your Page class, and then fire off an static event in your Page class that each instance could handle.

     

    I haven't tried it out, but it might be worth investigating...

     

    Cheers,

     

    John

    Monday, June 4, 2007 1:49 PM
  • Drew,

    I just wanted to add my vote to your suggestion that WPF should route at least commands (I don't have a use case for events) up to the Application as a last resort (so have CommandBindings on System.Windows.Application).  Our scenario is an application that used to be an MDI MFC app that we are moving to WPF.  We are breaking out of MDI in the WPF version, but the result is that we have an application with a lot of windows, none of which can be considered the "main" window.  When we were MFC, all our commands were handled by the MDI frame window and routed to the active window from there.  With WPF (at least as of 3.5 SP1), I see little choice but to put a set of command bindings on every single window.  If System.Windows.Application had CommandBindings, and the Application were treated as the handler-of-last-resort, we could put our one set of bindings at the Application level and be done.  Could you maybe peek at the .NET 4.0 sources and let me know if this got in ;-)?  Or if not, bug the devs again for 4.1?

    Thanks,

    Eric

    Eric
    Tuesday, April 7, 2009 5:34 AM
  • In re-reading my post above, I glanced up and saw John's suggestion to use CommandManager::RegisterClassCommandBinding.  All of the windows in my app are instances of the same Window-derived class, so today I tried putting class bindings on that class, and that worked great.  Having CommandBindings on the System.Windows.Application with that class being the command handler of last resort somehow seems like less of a hack (and somewhat more discoverable, since that's where I went first), but the class bindings allow me to achieve my goal of having one set of bindings for my application.

    Thanks,

    Eric
    Eric
    Tuesday, April 7, 2009 6:46 PM