Elad’s WPF Blog

April 8, 2009

DataBinding Tips & Tricks

Filed under: Programming, WPF — Tags: , — Elad Malki @ 10:40 am

Since DataBinding is one of the most important and useful features of WPF, here are some Tips & Tricks regarding it:

  • DataBinding CheatSheat  – This is a nice pdf summarizing most of DataBinding properties and options (via NBD Tech post)
  • Debugging Tips-
    • Look at the Visual Studio output window – It provides high level details about the binding and is sufficient to “easy” problems like naming mismatch: If we want to bind to this Person class:
          class Person
          {
               public string FirstName { get; set; }
               public string LastName { get; set; }
          }

    and have this XMAL:

           <TextBlock Text="{Binding Path=Name, Mode=OneWay}"/>

you will see the following error in the output window:

System.Windows.Data Error: 39 : BindingExpression path error: Name' property not found on 'object' ''Person' (HashCode=1056119)'. BindingExpression:Path=Name; DataItem='Person' (HashCode=1056119); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

TraceLevel is an AttachedProperty introduced on .net 3.5, which adds trace information to the VS output window. In order to use it, you first need to add this xml namespace:

           xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"

and then add the TraceLevel to the appropriate binding:

           <TextBlock Text="{Binding Path=Name, Mode=OneWay, diag:PresentationTraceSources.TraceLevel=High}"/>

The output window will contain a detailed trace:

System.Windows.Data Warning: 52 : Created BindingExpression (hash=35885827) for Binding (hash=37710717)

System.Windows.Data Warning: 54 :   Path: ‘Name’

System.Windows.Data Warning: 57 : BindingExpression (hash=35885827): Default update trigger resolved to PropertyChanged

System.Windows.Data Warning: 58 : BindingExpression (hash=35885827): Attach to System.Windows.Controls.TextBlock.Text (hash=13575069)

System.Windows.Data Warning: 63 : BindingExpression (hash=35885827): Resolving source

System.Windows.Data Warning: 66 : BindingExpression (hash=35885827): Found data context element: TextBlock (hash=13575069) (OK)

System.Windows.Data Warning: 67 : BindingExpression (hash=35885827): DataContext is null

System.Windows.Data Warning: 61 : BindingExpression (hash=35885827): Resolve source deferred

‘BindingSample.vshost.exe’ (Managed): Loaded ‘C:\WINDOWS\assembly\GAC_MSIL\PresentationFramework.Luna\3.0.0.0__31bf3856ad364e35\PresentationFramework.Luna.dll’, Skipped loading symbols. Module is optimized and the debugger option ‘Just My Code’ is enabled.

System.Windows.Data Warning: 63 : BindingExpression (hash=35885827): Resolving source

System.Windows.Data Warning: 66 : BindingExpression (hash=35885827): Found data context element: TextBlock (hash=13575069) (OK)

System.Windows.Data Warning: 74 : BindingExpression (hash=35885827): Activate with root item Person (hash=21577349)

System.Windows.Data Warning: 104 : BindingExpression (hash=35885827):   At level 0 – for Person.Name found accessor <null>

System.Windows.Data Error: 39 : BindingExpression path error: ‘Name’ property not found on ‘object’ ”Person’ (HashCode=21577349)’. BindingExpression:Path=Name; DataItem=’Person’ (HashCode=21577349); target element is ‘TextBlock’ (Name=”); target property is ‘Text’ (type ‘String’)

System.Windows.Data Warning: 76 : BindingExpression (hash=35885827): TransferValue – got raw value {DependencyProperty.UnsetValue}

System.Windows.Data Warning: 84 : BindingExpression (hash=35885827): TransferValue – using fallback/default value ”

System.Windows.Data Warning: 85 : BindingExpression (hash=35885827): TransferValue – using final value ”

  • Use an empty converter – You can set a converter on your binding, and set a breakpoint in the Convert\ConvertBack function. you can find a nice implementation of this in this post
    • Snoop it – Using Snoop is always a good option for getting info about your app.
      • The “Binding Errors” column can provide binding related info.
      • The DataContext DP can provide info about the binding source.
    • Strings Formatting –

    StringFormat is a .NET 3.5 SP1 property which may be used for formatting strings in binding. For example, instead of using this:

          <WrapPanel>
                <TextBlock Text="{Binding Path=FirstName, Mode=OneWay}"/>
                <TextBlock Text=" "/>
                <TextBlock Text="{Binding Path=LastName, Mode=OneWay}"/>
          </WrapPanel>

    You may use this:

           <WrapPanel>
                <TextBlock>
                    <TextBlock.Text>
                        <MultiBinding StringFormat="{}{0} {1}">
                            <Binding Path="FirstName" Mode="OneWay"/>
                            <Binding Path="LastName" Mode="OneWay"/>
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>
           </WrapPanel>

    The benefit here is double, there is only one binding instead of two, and only on TextBlock element instead of three. This may help performance especially when used as a DataTemplate of an ItemsControl with many items.

    You may of course use the StringFormat for any other formatting you need (dates, currencies, etc.)

    • Virtualizing –

    When binding to a large collection, consider using the following attached properties:

    • Explicitly state the Binding Mode – The default binding mode is set by binding target property type, which might not always be what you imagine… Besides, it contributes to your XAML\code readability. Of course, preferring the OneTime mode (If appropriate to your scenario) is best for performance.
    • Try using less converters if performance is critical – Instead of using a converter which requires Boxing \ Unboxing, you can create a property in you ViewModel, do the converter in the getter, and bind to it.
    • Binding.DoNothing – May be useful as return value in a converter when you have a scenario where you don’t want to “interfere” in the binding process (and use the FallbackValue for example).
    • Binding in a non-visual tree element – Sometimes, a binding is needed in an element which does not inherit a context. most of the times, you will see the following error in this case:

    System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element…

    There are several “tricks” to overcome this problem:

    April 2, 2009

    Animated GIF Support Behavior

    Filed under: Programming, WPF — Tags: , , , — Elad Malki @ 4:50 pm

    After talking about Attached Behavior in the last post, this is an example of a behavior which adds an animated GIF support to regular WPF Image control.

    WPF does not support naturally displaying an animated GIF. There are couple of ways to overcome this problem, and using this behavior is one of them. Basically, the behavior creates an animation that loops over the animated gif frames, setting them as a source for the image.

    you can download the source and sample here

    The usage example is:

    <Window x:Class="AnimatedGifBehaviorTest.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:AnimatedGifBehaviorTest"
            
        Title="Window2" Height="300" Width="300">
        <Grid>
                <Image local:SupportAnimatedGIFBehviour.SupportAnimatedGif="True"
                       Source="MyImage.gif" Width="32" Height="32"/>
        </Grid>
    </Window>

    And the behavior implementation is:
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    
    namespace AnimatedGifBehaviorTest
    {
        public static class SupportAnimatedGIFBehviour
        {
            private static readonly int MILLISCONDS_PER_FRAME = 75;
    
            #region SupportAnimatedGif Attached Property
    
            [AttachedPropertyBrowsableForType(typeof(Image))]
            public static bool GetSupportAnimatedGif(Image image)
            {
                return (bool)image.GetValue(SupportAnimatedGifProperty);
            }
    
            public static void SetSupportAnimatedGif(Image image, bool value)
            {
                image.SetValue(SupportAnimatedGifProperty, value);
            }
    
            /// <summary>
            /// An Attached Property for Animated GIF support.
            /// </summary>
            public static readonly DependencyProperty SupportAnimatedGifProperty =
                DependencyProperty.RegisterAttached(
                "SupportAnimatedGif",
                typeof(bool),
                typeof(SupportAnimatedGIFBehviour),
                new UIPropertyMetadata(false, OnSupportAnimatedGifChanged));
    
            /// <summary>
            /// Register \ UnRegister to visibility changes and image source changes.
            /// </summary>
            /// <param name="depObj">The image object.</param>
            /// <param name="e">The SupportAnimatedGif Property values.</param>
            private static void OnSupportAnimatedGifChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
            {
                bool isSupportingAnimatedGIF = (bool)e.NewValue;
                Image image = (Image)depObj;
    
                if (isSupportingAnimatedGIF)
                {
                    RegisterForRelevantImagePropertyChanges(image);
                    
                    if (image.Visibility == Visibility.Visible)
                    {
                        image.StartFramesAnimation();
                    }
                }
                else
                {
                    UnRegisterForRelevantImagePropertyChanges(image);
                    StopFramesAnimation(image);
                }
            }
    
            private static DependencyPropertyDescriptor VisibilityDPDescriptor
            {
                get
                {
                    return DependencyPropertyDescriptor.FromProperty(UIElement.VisibilityProperty, typeof(UIElement));
                }
            }
    
            private static DependencyPropertyDescriptor ImageSourceDPDescriptor
            {
                get
                {
                    return DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof(Image));
                }
            }
    
            private static void UnRegisterForRelevantImagePropertyChanges(DependencyObject depObj)
            {
                VisibilityDPDescriptor.RemoveValueChanged(depObj, OnVisibilityChanged);
                ImageSourceDPDescriptor.RemoveValueChanged(depObj, OnImageSourceChanged);            
            }
    
            private static void RegisterForRelevantImagePropertyChanges(DependencyObject depObj)
            {
                VisibilityDPDescriptor.AddValueChanged(depObj, OnVisibilityChanged);
                ImageSourceDPDescriptor.AddValueChanged(depObj, OnImageSourceChanged);
            }
    
            #endregion
    
            #region CurrentFrameIndex Dependency Property
    
            private static readonly DependencyProperty CurrentFrameIndexProperty = DependencyProperty.Register(
                "CurrentFrameIndex",
                typeof(int),
                typeof(Image),
                new PropertyMetadata(0, OnCurrentFrameIndexChanged));
    
            #endregion
    
            #region IsAnimationChangingFrame Dependency Property
    
            /// <summary>
            /// IsAnimationChangingFrame Dependency Property
            /// </summary>
            private static readonly DependencyProperty IsAnimationChangingFrameProperty = DependencyProperty.Register(
                "IsAnimationChangingFrame",
                typeof(bool),
                typeof(Image),
                new PropertyMetadata(false));
    
            /// <summary>
            /// DummyImage Dependency Property - for keeping the original source binding when animation is applied
            /// </summary>
            private static readonly DependencyProperty DummyImageProperty = DependencyProperty.Register(
                "DummyImage",
                typeof(ImageSource),
                typeof(Image),
                new PropertyMetadata(null, OnDummyImagePropertyChanged));
    
            /// <summary>
            /// Gets or sets the IsAnimationChangingFrame property. This dependency property 
            /// indicates that the animation is currently changing the image source.
            /// </summary>
            private static bool GetIsAnimationChangingFrame(this Image image)
            {
                return (bool)image.GetValue(IsAnimationChangingFrameProperty);
            }
            private static void SetIsAnimationChangingFrame(this Image image, bool isAnimationChangingFrame)
            {
                image.SetValue(IsAnimationChangingFrameProperty, isAnimationChangingFrame);
            }
    
            #endregion
    
            private static void OnImageSourceChanged(object sender, EventArgs e)
            {
                Image image = (Image)sender;
    
                if (!image.GetIsAnimationChangingFrame())//If the image source is changing by an outer source(not the animation).
                {
                    //stop old animation
                    image.SetIsAnimationChangingFrame(true);
                    image.StopFramesAnimation();
                    image.SetIsAnimationChangingFrame(false);
    
                    //start new animation - only if frames count is bigger than one.
                    image.StartFramesAnimation();
                }
            }
    
            /// <summary>
            /// Starts frame animation only if frames count is bigger than 1.
            /// </summary>
            /// <param name="image"></param>
            private static void StartFramesAnimation(this Image image)
            {
                BitmapFrame bitmapFrame = image.Source as BitmapFrame;
                if (bitmapFrame != null)
                {
                    int framesCount = bitmapFrame.Decoder.Frames.Count;
    
                    if (framesCount > 1)
                    {
                        Int32Animation gifAnimation =
                            new Int32Animation(
                                0, // "From" value
                                framesCount - 1, // "To" value
                                new Duration(TimeSpan.FromMilliseconds(MILLISCONDS_PER_FRAME * framesCount))
                            );
    
                        gifAnimation.RepeatBehavior = RepeatBehavior.Forever;
    
                        image.BeginAnimation(CurrentFrameIndexProperty, gifAnimation, HandoffBehavior.SnapshotAndReplace);
                    }
                }
            }
    
            private static void StopFramesAnimation(this Image image)
            {
                image.BeginAnimation(CurrentFrameIndexProperty, null);
            }
    
            private static void OnVisibilityChanged(object sender, EventArgs e)
            {
                Image image = (Image)sender;
    
                if (image.Visibility != Visibility.Visible)
                {
                    image.StopFramesAnimation();
                }
                else
                {
                    image.StartFramesAnimation();
                }
            }
    
            private static void OnDummyImagePropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                Image animatedImage = (Image)dpo;
    
                if (!animatedImage.GetIsAnimationChangingFrame())
                {
                    BindingBase originalBinding = BindingOperations.GetBindingBase(dpo, DummyImageProperty);
                    if (originalBinding != null)
                    {
                        BindingOperations.SetBinding(dpo, Image.SourceProperty, originalBinding);
                        BindingOperations.ClearBinding(animatedImage, DummyImageProperty);
                    }
                    animatedImage.SetIsAnimationChangingFrame(false);
                }
                else
                {
                    animatedImage.SetIsAnimationChangingFrame(false);
                }
    
                animatedImage.SetIsAnimationChangingFrame(false);
            }
    
            /// <summary>
            /// Update the current image source to the relevenat frame.
            /// </summary>
            /// <param name="dpo"></param>
            /// <param name="e"></param>
            private static void OnCurrentFrameIndexChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
            {
                Image animatedImage = (Image)dpo;
    
                if (!animatedImage.GetIsAnimationChangingFrame())
                {
                    animatedImage.SetIsAnimationChangingFrame(true);
    
                    bool hasBinding = BindingOperations.IsDataBound(animatedImage, Image.SourceProperty);
                    if (hasBinding)
                    {
                        BindingBase originalBinding = BindingOperations.GetBindingBase(animatedImage, Image.SourceProperty);
                        BindingOperations.SetBinding(animatedImage, DummyImageProperty, originalBinding);
                    }
                    animatedImage.Source = ((BitmapFrame)animatedImage.Source).Decoder.Frames[(int)e.NewValue];
    
                    if (!hasBinding)
                    {
                        animatedImage.SetIsAnimationChangingFrame(false);
                    }
                }
            }
        }
    }

    Attached Behavior

    Filed under: Programming, WPF — Tags: , , — Elad Malki @ 8:04 am

    WPF has a powerful concept of Attached Properties. Since a Dependency Object is a kind of a sophisticated property bag, it can contain its own properties and other properties. The traditional use of attached properties is by the object that declare it, for example, A grid has a Row and a Column attached properties. It looks up these properties on every child of his, and use their values to arrange the children:

    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Button Grid.Row="0">Button 1</Button>
      <Button Grid.Row="1">Button 2</Button>
    </Grid>

    A more advanced way of using attached properties is Attached Behaviors. An attached behavior is a way of extending or adding functionality of a dependency object (you can find more about it in Josh Smith’s article). For example, we might have a scenario in which we want to select the text of a TextBox when it gets the focus. A solution for this scenario could be hooking to the appropriate events in the code behind \ ViewModel of the TextBox and selecting the text when needed. There are two problems with this solution:

    1. It requires writing code in an "inappropriate" WPF places such as code behind (which is always better to leave empty) or a ViewModel (which should contain info and logic of the view rather than technical stuff).

    2. It requires writing a lot of code many times if we have more than one textbox, or several textboxes in different windows that needs this behavior.

    A better solution would be writing an attached behavior. An attached behavior in WPF is normally a static class with one or more attached properties. The trick in this class is to listen to the change event of one of the properties, and to act upon it. This is an example of the Textbox "text selection on focus" behavior (Based on Eric Burke’s Post):

    public static class SelectAllOnFocusBehaviour
    {
            /// 1. This is the boolean attached property with its getter and setter:
            public static readonly DependencyProperty SelectAllOnFocusProperty =
                DependencyProperty.RegisterAttached
                (
                    "SelectAllOnFocus",
                    typeof(bool),
                    typeof(SelectAllOnFocusBehaviour),
                    new UIPropertyMetadata(false, OnSelectAllOnFocusPropertyChanged)
                );
            public static bool GetSelectAllOnFocus(DependencyObject obj)
            {
                return (bool)obj.GetValue(SelectAllOnFocusProperty);
            }
            public static void SetSelectAllOnFocus(DependencyObject obj, bool value)
            {
                obj.SetValue(SelectAllOnFocusProperty, value);
            }
    
            /// 2. This is the change event of our attached property value:
            ///     * We get in the first parameter the dependency object to which the attached behavior was attached
            ///     * We get in the second parameter the value of the attached behavior.
            ///     * The implementation of the behavior is to check if we are attached to a textBox, and if so and the value of the behavior
            ///       is true, hook to the PreviewGotKeyboardFocus of the textbox.
            private static void OnSelectAllOnFocusPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
            {
                TextBoxBase textBox = dpo as TextBoxBase;
                if (textBox != null)
                {
                    TextBoxBase tbb = dpo as TextBoxBase;
                    if ((bool)args.NewValue)
                    {
                        textBox.PreviewGotKeyboardFocus += OnTextBoxPreviewGotKeyboardFocus;
                    }
                    else
                    {
                        textBox.PreviewGotKeyboardFocus -= OnTextBoxPreviewGotKeyboardFocus;
                    }
                }
            }
    
            /// 3. The actual implementation: Whenever the textbox gets the keyboard focus, we select its text
            private static void OnTextBoxPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
            {
                TextBoxBase txtBox = (TextBoxBase)sender;
                Action action = () => { txtBox.SelectAll(); };
                txtBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
            }
        }

    And this is an example of the behavior usage:

    <Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:behaviors="your behavior xmlns path">
      <Grid>
        <TextBox Text="Some Text" behaviors:SelectAllOnFocusBehaviour.SelectAllOnFocus="True"/>
      </Grid>
    </Page>

    The result is a much declarative, maintainable and shorter code. You can find many useful behaviors on the web.

    In Silverlight 3.0 there is a built in behaviors infrastructure, by using the Behaviors (and\or Triggers) attached property which contains a collection of behaviors (or Triggers).

    Elad.

    March 30, 2009

    WPF Tools

    Filed under: WPF — Tags: , , , — Elad Malki @ 1:31 pm

    Hi,

    My name is Elad Malki and I’m a software developer at HP Software IL. This is the first post of my first blog, which will be mostly dealing with WPF.

    Now that Expression Blend 3.0 Preview is out, it is time to update my list of WPF dev tools:

    • KAXAML – A XAML Parser for quick visualization of the basic built-in WPF UI elements
    • Reflector+BAML Viewer add-in – An Add-in for the reflector that loads BAML from an assembly and display it XAML Syntax
    • Pistachio – A WPF Resource Visualizer for identifying non-usable resources
    • Snoop & Mole – Visual Tree Explorer – Snoop is a standalone app, while mole is a VS debugger visualizer (I just use snoop actually…)

    You can find some additional tools here: http://wpf-resources.com/wpftools.aspx If you know about other useful tools, please share !

    Blog at WordPress.com.

    Follow

    Get every new post delivered to your Inbox.