vspivak

Just another WordPress.com site

Sometimes I am going mad, mad, mad!

Problem with Microsoft.Expression.Interactivity.Core.DataTrigger

It looks like DataTrigger have some annoying bug – it does not respect FrameworkElement loading sequence and this way misses those bindings that are, for example, resource bound.

This will not work


<i:Interaction.Triggers>
      <Core:DataTrigger Binding="{Binding Source={StaticResource MeasurementData}, Path=IsMultiSite}" 
                                  Value="False" Comparison="Equal">
               <Core:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" TargetName="MyElement"/>
      </Core:DataTrigger>
</i:Interaction.Triggers>

The solution is slightly extended version of DataTrigger


using System.Windows;

namespace Microsoft.Expression.Interactivity.Core
{
    public class XDataTrigger:DataTrigger
    {
        public bool RespectLoadedEvent
        {
            get { return (bool) GetValue(RespectLoadedEventProperty); }
            set { SetValue(RespectLoadedEventProperty, value); }
        }

        public static readonly DependencyProperty RespectLoadedEventProperty =
            DependencyProperty.Register("RespectLoadedEvent", typeof (bool), typeof (XDataTrigger), new PropertyMetadata(true, OnRespectLoadedEventChanged));

        private static void OnRespectLoadedEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
        }

        protected override void OnAttached()
        {   
            if(AssociatedObject is FrameworkElement && RespectLoadedEvent)
                ((FrameworkElement)AssociatedObject).Loaded += XDataTriggerLoaded;
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            if (AssociatedObject is FrameworkElement)
                ((FrameworkElement)AssociatedObject).Loaded -= XDataTriggerLoaded;

        }

        void XDataTriggerLoaded(object sender, RoutedEventArgs e)
        {
           EvaluateBindingChange(null);
        }
    }
}

Enjoy!

Using System.Windows.Interactivity Behaviors and Actions in WPF/Silverlight styles

You may somehow disagree, but System.Windows.Interactivity is good approach not only in Blend, but also in frontal WPF/Silverlight development. Recently I  finally and completely stopped to use ICommandSource directly (I mean <Button Command=”<blabla>”/>) and instead I am using Interactivity.EventTrigger on “Click” event with appropriate action. But Interactivity facilities have some caveat preventing convenient use from Style setters – both Triggers and Behaviors are read-only properties. So, I can’t write some xaml like this:

<!--NOTE: this code is not working!-->
<Setter Property="int:Interaction.Triggers">
    <TriggerCollection>
         <int:EventTrigger EventName="MouseMove">
             <int:TargetedSetPropertyAction 
                                PropertyName="Text" 
                                TargetObject="{Binding ElementName=_tblock}" 
                                 Value="{Binding}">
             </int:TargetedSetPropertyAction>
         </int:EventTrigger>
    </TriggerCollection>
</Setter>

Ok, but I really want this working! So lets find a solution that works in both WPF and Silverlight. At a first glance the solution seemed simple: fill some lists on your defined object, use some attached property and in it’s OnPropertyChange notifier copy your interaction elements into actual Interactivity lists:


// NOTE: INVALID CODE - DON'T USE IT
using System.Collections.Generic;

namespace System.Windows.Interactivity
{
    public class InteractivityItems 
    {
        private List<Behavior> _behaviors;
        private List<TriggerBase> _triggers;

        public List<TriggerBase> Triggers
        {
            get
            {
                if (_triggers == null)
                    _triggers = new List<TriggerBase>();
                return _triggers;
            }
        }

        public List<Behavior> Behaviors
        {
            get
            {
                if (_behaviors == null)
                    _behaviors = new List<Behavior>();
                return _behaviors;
            }
        }

        #region Template attached property

        public static InteractivityItems GetTemplate(DependencyObject obj)
        {
            return (InteractivityItems)obj.GetValue(TemplateProperty);
        }

        public static void SetTemplate(DependencyObject obj, InteractivityItems value)
        {
            obj.SetValue(TemplateProperty, value);
        }

        public static readonly DependencyProperty TemplateProperty =
            DependencyProperty.RegisterAttached("Template", 
                         typeof(InteractivityItems), 
                         typeof(InteractivityItems),
                         new PropertyMetadata(default(InteractivityItems), OnTemplateChanged));

        private static void OnTemplateChanged(DependencyObject d, 
                                                             DependencyPropertyChangedEventArgs e)
        {
            InteractivityItems ih = (InteractivityItems)e.NewValue;
            BehaviorCollection bc = Interaction.GetBehaviors(d);
            TriggerCollection tc = Interaction.GetTriggers(d);

            foreach (Behavior behavior in ih.Behaviors)
                bc.Add(behavior);

            foreach (TriggerBase trigger in ih.Triggers)
                tc.Add(trigger);
        }

        #endregion
    }
}

… and xaml is…

<!--INVALID CODE - DON'T USE IT-->
   <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" x:Name="_tblock" Text="Default"></TextBlock>
        <ListBox ItemsSource="{Binding DataSource}" Grid.Column="0">
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="int:InteractivityItems.Template">
                        <Setter.Value>
                            <int:InteractivityItems>
                                <int:InteractivityItems.Behaviors>
                                    <int:FlipOnHover></int:FlipOnHover>
                                </int:InteractivityItems.Behaviors>
                                <int:InteractivityItems.Triggers>
                                    <int:EventTrigger EventName="MouseMove">
                                        <Core:ChangePropertyAction 
                                                 PropertyName="Text" 
                                                 TargetObject="{Binding ElementName=_tblock}" 
                                                 Value="{Binding}">
                                        </Core:ChangePropertyAction>
                                    </int:EventTrigger>
                                </int:InteractivityItems.Triggers>
                            </int:InteractivityItems>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>

As you can understand from the comment, such approach is not working as expected. There are two reasons for that. The first reason is that our definition is Style-wide, so we actually adding the same instances of Actions and Behaviors to all instances of elements this style will be applied to. Obviously, this is not supported – each Behaviour or Trigger instance may have only one AssociatedObject. The second issue, that Triggers and Behaviors usually are Binding-rich and those bindings will be evaluated at the moment when style will be instantiated instead of the moment when style will be actually applied to element.
Fortunately, there is built-it mechanism in both WPF and Silverlight that supports later evaluation of object tree including all markup definitions such as bindings, resource accessors or such – FrameworkTemplate.
There are 3 built-in subclasses of FrameworkTemplate – DataTemplate, ControlTemplate and ItemPanelTemplate. In current version of WPF we can not subclass FrameworkTemplate directly due to existence of some abstract internal member. But instead we can subclass DataTemplate. So working solution will look like the follows:

using System.Collections.Generic;

namespace System.Windows.Interactivity
{
    /// <summary>
    /// <see cref="FrameworkTemplate"/> for InteractivityElements instance
    /// <remarks>Subclassed for forward compatibility, perhaps one day <see cref="FrameworkTemplate"/> </remarks>
    /// <remarks>will not be partially internal</remarks>
    /// </summary>
    public class InteractivityTemplate : DataTemplate
    {

    }

    /// <summary>
    /// Holder for interactivity entries
    /// </summary>
    public class InteractivityItems : FrameworkElement
    {
        private List<Behavior> _behaviors;
        private List<TriggerBase> _triggers;

        /// <summary>
        /// Storage for triggers
        /// </summary>
        public List<TriggerBase> Triggers
        {
            get
            {
                if (_triggers == null)
                    _triggers = new List<TriggerBase>();
                return _triggers;
            }
        }

        /// <summary>
        /// Storage for Behaviors
        /// </summary>
        public List<Behavior> Behaviors
        {
            get
            {
                if (_behaviors == null)
                    _behaviors = new List<Behavior>();
                return _behaviors;
            }
        }

        #region Template attached property

        public static InteractivityTemplate GetTemplate(DependencyObject obj)
        {
            return (InteractivityTemplate)obj.GetValue(TemplateProperty);
        }

        public static void SetTemplate(DependencyObject obj, InteractivityTemplate value)
        {
            obj.SetValue(TemplateProperty, value);
        }

        public static readonly DependencyProperty TemplateProperty =
            DependencyProperty.RegisterAttached("Template", 
            typeof(InteractivityTemplate), 
            typeof(InteractivityItems),
            new PropertyMetadata(default(InteractivityTemplate), OnTemplateChanged));

        private static void OnTemplateChanged(
            DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            InteractivityTemplate dt = (InteractivityTemplate)e.NewValue;
#if(!SILVERLIGHT)
            dt.Seal();
#endif
            InteractivityItems ih = (InteractivityItems)dt.LoadContent();
            BehaviorCollection bc = Interaction.GetBehaviors(d);
            TriggerCollection tc = Interaction.GetTriggers(d);

            foreach (Behavior behavior in ih.Behaviors)
                bc.Add(behavior);

            foreach (TriggerBase trigger in ih.Triggers)
                tc.Add(trigger);


        }

        #endregion
    }
}

… and actual usage will look like…

    <Grid>        
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" x:Name="_tblock" 
                   Text="Default" 
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" 
                   FontSize="24" 
                   FontWeight="Bold">            
        </TextBlock>
        <ListBox ItemsSource="{Binding Source={StaticResource Model},Path=DataSource}" 
                 Grid.Column="0"
                 HorizontalAlignment="Center" 
                 VerticalAlignment="Center">
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="FontSize" Value="24"/>
                    <Setter Property="FontWeight" Value="Bold"/>
                    <Setter Property="int:InteractivityItems.Template">
                        <Setter.Value>
                            <int:InteractivityTemplate>
                                <int:InteractivityItems>
                                    <int:InteractivityItems.Behaviors>
                                        <int:FlipOnHover></int:FlipOnHover>
                                    </int:InteractivityItems.Behaviors>
                                    <int:InteractivityItems.Triggers>
                                        <ie:EventTrigger EventName="MouseMove">
                                            <Core:ChangePropertyAction
                                                PropertyName="Text"
                                                TargetObject="{Binding ElementName=_tblock}"
                                                Value="{Binding}">
                                            </Core:ChangePropertyAction>
                                        </ie:EventTrigger>
                                    </int:InteractivityItems.Triggers>
                                </int:InteractivityItems>
                            </int:InteractivityTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    </Grid>

Sample for both WPF and Silverlight is here (rename DOC to ZIP)

Follow

Get every new post delivered to your Inbox.