13 April 2011

Preventing objects made invisible by data binding to show up initially

Currently I am in the process of writing a small Windows Phone 7 game . It sports a ‘game over’ panel with a ‘play again’ button and it’s visibility is controlled by a boolean value ‘IsGameOver’ in the ViewModel. My VisibilityConverter converts the true and false to Visiblity.Visible and Visibility.Collapsed.

Although this works well, data binding apparently takes place only after the complete GUI is loaded, for when the game starts, you see the panel for about half a second before it disappears again. Ugh.

I did not actually solve this problem, but found a workaround in the following little behavior:

using System.Windows;
using System.Windows.Interactivity;

namespace Wp7nl.Behaviors
{
  public class SetInitialOpacityBehavior : Behavior<FrameworkElement>
  {
    protected override void OnAttached()
    {
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      base.OnAttached();
      AssociatedObject.Opacity = InitialOpacity;
    }

    void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      AssociatedObject.Opacity = OpacityAfterLoading;
    }

    #region InitialOpacity
    public const string InitialOpacityPropertyName = "InitialOpacity";

    public int InitialOpacity
    {
      get { return (int)GetValue(InitialOpacityProperty); }
      set { SetValue(InitialOpacityProperty, value); }
    }

    public static readonly DependencyProperty InitialOpacityProperty = 
	  DependencyProperty.Register(
        InitialOpacityPropertyName,
        typeof(int),
        typeof(SetInitialOpacityBehavior),
        new PropertyMetadata(0));
    #endregion

    #region OpacityAfterLoading
    public const string OpacityAfterLoadingPropertyName = "OpacityAfterLoading";

    public int OpacityAfterLoading
    {
      get { return (int)GetValue(OpacityAfterLoadingProperty); }
      set { SetValue(OpacityAfterLoadingProperty, value); }
    }

    public static readonly DependencyProperty OpacityAfterLoadingProperty = 
	DependencyProperty.Register(
      OpacityAfterLoadingPropertyName,
      typeof(int),
      typeof(SetInitialOpacityBehavior),
      new PropertyMetadata(1));
    
    #endregion
  }
}

It basically sets the opacity to 0 when the behavior is attached to the FrameworkElement (which apparently takes place before the element is actually displayed), thereby making the element invisible. After the Loading event has fired, it sets the opacity to the value of the dependency property OpacityAfterLoading - which is default 1. Apparently this takes place after data binding. Whatever – my ‘game over’ panel does not show up at the start anymore, but only after the game ends. Like it should.

So if you notice an element showing up initially while it should not, just fire up Blend, drag this behavior on top of it and be done with it.

This behavior is by no means Windows Phone 7 specific – it would work perfectly in Silverlight and probably WPF too. Be aware, however, that although OpacityAfterLoading is a dependency property, in Windows Phone 7 you cannot data bind it, because it’s currently based on Silverlight 3 which only allow you to bind to classes that do descend from DependencyObject – which a behavior obviously does not ;-)

One final concluding note – although this behavior is in the Wp7nl namespace, it currently not in the Wp7nl library nor the NuGet package. But it will be in the next ;-)

2 comments:

Unknown said...

Does it work to use the TargetNullValue in the binding to provide an initial value of Collapsed?

Joost van Schaik said...

@chrisaswain apparently not in Windows Phone 7 ;-)