Tuesday, January 27, 2009

Hide a window instead of closing it in WPF

Hiding a window instead of closing it is especially useful when the window is a singleton. This is a typical situation for "options" windows which store application settings. Balaji Ramesh proposed solution to this problem, which can be found here.

    2 // Handle closing event to hide window instead of closing it
    3 Closing += delegate(object sender, CancelEventArgs e)
    4 {
    5     e.Cancel = true;
    7     Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
    8         (DispatcherOperationCallback)(arg =>
    9     {
   10         Hide();
   11         return null;
   12     }), null);
   13 };

As you can see, the code is extremely simple. Everything it does is to subscribe to Window's Closing event (which is not a routed one), suppress the close action and invoke Window's Hide() method in the UI thread to actually hide the window. We could augment this code with Hiding and Hidden routed events to inform interested entities the window is hiding and when the window is hidden. Using Hiding event it's very easy to cancel window hiding as well.

But what would you do if you had dozens of windows which you would hide instead of close? Typing this code into every window constructor is by no means an option. The neat solution (at least in my opinion) is to implement this functionality as an Attached Behavior.

    1 public static readonly DependencyProperty HideInsteadCloseProperty =
    2     DependencyProperty.RegisterAttached("HideInsteadClose", 
    3     typeof(bool), typeof(WindowBehavior), new FrameworkPropertyMetadata(
    4         new PropertyChangedCallback(OnHideInsteadClose)));
    7 private static void OnHideInsteadClose(DependencyObject d,
    8     DependencyPropertyChangedEventArgs e)
    9 {
   10     var window = d as Window;
   11     if (window != null)
   12     {
   13         if ((bool)e.NewValue)
   14         {
   15             // Handle closing event to hide window instead of closing it
   16             window.Closing += delegate(object sender, CancelEventArgs args)
   17             {
   18                 args.Cancel = true;
   20                 window.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
   21                     (DispatcherOperationCallback)delegate
   22                 {
   23                     var cancelArgs = new CancelRoutedEventArgs(HidingEvent, window);
   24                     window.RaiseEvent(cancelArgs);
   26                     if (!cancelArgs.Cancel)
   27                     {
   28                         window.Hide();
   29                         window.RaiseEvent(new RoutedEventArgs(HiddenEvent, window)); 
   30                     }
   32                     return null;
   33                 }, null);
   34             };
   35         }
   36     }
   37 }

The core concept behind the above implementation is to subscribe to the Window's Close event inside HideInsteadClose attached property's change callback. Besides, we can implement Hiding and Hidden attached routed events. (this has been omitted for clarity). Using this code is as simple as adding appropriate code as Window's attribute in XAML as follows:

Common:WindowBehavior.HideInsteadClose="True" Common:WindowBehavior.Hidden="HiddenHandler"

The code is available for download from my Code Gallery here.

1 comment:

lee woo said...

If we ever do end up acting just like a rats or pavlov's dogs, it will be largely because behaviorism has conditioned us to do so. See the link below for more info.