none
Binding to ActualWidth from within a ControlTemplate

    Question

  • I have a custom class (derived from ContentControl) that holds two children. The purpose of this class is to allow for transitions to be executed between the two children. Transitions are written as ControlTemplate resources, and are swapped by just changing the Template on the custom control.

     

    Overall this works fine. However, there are some specific transition effects that cause difficulty. For example, a slide transition requires setting the RenderTransform on one of the children to a TranslateTransform whose X value is set to the width of the control (so it can be animated to slide in from the right edge to 0). I can hard-code the X value and everything works fine. However, if I want to get the actual width of the control, I run into various difficulties. Here is a subset of the XAML for the slide transition:

     

    <!--Slide Template-->
      <ControlTemplate TargetType="{x:Type local:ABSwitcher}" x:Key="SlideTemplate">
        <Grid Name="ElementGrid">
          <ContentPresenter Name="ElementAPresenter" Visibility="Visible" Content="{TemplateBinding ElementA}"/>
          <ContentPresenter Name="ElementBPresenter" Visibility="Visible" Content="{TemplateBinding ElementB}"/>
        </Grid>
        <ControlTemplate.Triggers>
          <Trigger Property="local:ABSwitcher.CurrentElement" Value="ElementA">
             <Setter TargetName="ElementAPresenter" Property="Grid.ZIndex" Value="1"/>
             <Setter TargetName="ElementAPresenter" Property="RenderTransform">
               <Setter.Value>
                 <TranslateTransform X="800" />
               </Setter.Value>
             </Setter>
             <Trigger.EnterActions>
               <BeginStoryboard >
                  <Storyboard>
                     <DoubleAnimation
                                        Storyboard.TargetName="ElementAPresenter"
                                        Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)"
                                        To="0" Duration="{StaticResource animationTime}"
                                        DecelerationRatio="0.5" />
                   </Storyboard>
               </BeginStoryboard>
           </Trigger.EnterActions>
        </Trigger>

        ...

    This works fine, except that the width is hard-coded. I would like to be able to do something like this for the TranslateTransform:

     

       <TranslateTransform X="{Binding ElementName=ElementGrid, Path=ActualWidth}" />

     

    But this returns 0. I am fairly sure that the reason for this is because the transform is not in the visual or logical tree, but I cannot find any notation that allows me to specify a relevant context (and short of getting 10,000 monkeys working the keyboard, I think I have tried just about everything).

     

    I also tried binding to an object like this:

     

    <TranslateTransform X="{Binding Source={StaticResource controlWidthValue},Path=DoubleValue,Mode=OneWay}" />

     

    Where controlWidthValue refers to an object that implements INotifyPropertyChanged. This works once, then stops. The DoubleValue property ends up being called during the first animation, but never again.

     

    So, here are my questions:

     

    1. It seems like there should be a fairly simple way to get access to properties like the ActualWidth here - what am I missing?

    2. With the bound object, why does it only get called once?

     

    Many thanks,

    Cowthulu

     

    Friday, August 17, 2007 11:12 PM

Answers

  • Those errors, especially the second one, suggest that that problem is that the TranslateTransform is being shared by two (or more) "owners", so that it's ambiguous which Grid (or named element) is the one the binding should use.  This sharing could arise from having two or more instances of the template, or it could be something more subtle - even stuff internal to WPF.

     

    When this situation arises in a style or template, WPF makes a copy of the TranslateTransform so that each instance has only one owner.   It's possible that this work isn't being done for triggers (which would be a bug).

     

    Sorry to be so hypothetical about this (too many "could be"s and "possible'"s).  I can find out for sure on Monday when I get back to the office.  Just wanted to get this thread on my radar, in case it's really a bug.

    Saturday, August 18, 2007 8:24 PM

All replies


  • i am not sure if this would work for you but try it out


    X="{Binding Path=ActualWidth,RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"


    Regards
    Saturday, August 18, 2007 9:48 AM
  • Afraid not. With that, I get the error:

     

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Grid', AncestorLevel='1''. BindingExpressionStick out tongueath=ActualWidth; DataItem=null; target element is 'TranslateTransform' (HashCode=25035015); target property is 'X' (type 'Double')

     

    Which is at least different than the error I get trying to bind by name:

     

    System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpressionStick out tongueath=ActualWidth; DataItem=null; target element is 'TranslateTransform' (HashCode=19634871); target property is 'X' (type 'Double')

     

     

     

    Saturday, August 18, 2007 6:05 PM
  • Those errors, especially the second one, suggest that that problem is that the TranslateTransform is being shared by two (or more) "owners", so that it's ambiguous which Grid (or named element) is the one the binding should use.  This sharing could arise from having two or more instances of the template, or it could be something more subtle - even stuff internal to WPF.

     

    When this situation arises in a style or template, WPF makes a copy of the TranslateTransform so that each instance has only one owner.   It's possible that this work isn't being done for triggers (which would be a bug).

     

    Sorry to be so hypothetical about this (too many "could be"s and "possible'"s).  I can find out for sure on Monday when I get back to the office.  Just wanted to get this thread on my radar, in case it's really a bug.

    Saturday, August 18, 2007 8:24 PM
  • Thanks - I would be thrilled if this was a bug (because it would mean that I wasn't crazy :-).

    If you would like me to send you the entire project, let me know.


    Sunday, August 19, 2007 3:41 AM
  • Sam -

    Did you have a chance to look at this?

    Thanks,

    Arlen
    Thursday, August 23, 2007 5:49 AM
  • Sorry it took me so long.  This is a very confusing scenario, and I've had other work to attend to as well.  But I think I understand it now.

     

    It looks like there are at least two bugs here, both completely different from my earlier conjectures.  The bugs are:

     

    1. The trigger launches both the binding and the animation.  As they start up, the animation is requesting information from the binding before the binding is ready to provide it.  The result is that the animation uses 0.0 as its From value, instead of the width of the grid.

     

    2. The animation caches its final value and uses it as the From value the second time you invoke the trigger, instead of starting over with the data-bound value.

     

    The second bug is why your alternative attempt works the first time, but not thereafter.

     

    As for workarounds, you've already found one for the first bug.  I can't suggest a workaround for the second bug.   I tried setting FillBehavior=Stop on the animation;  this makes the animation start with the correct value each time you launch the trigger (good), but reverts the property to its pre-animated value when the clock runs down (bad).

     

    I'm opening bugs about both issues.  Thanks for the report.

    Friday, August 24, 2007 9:33 PM

  • Sam -

    Thanks. At the very least I can write the section as though it will work, and then come back later and confirm it with the final release! Does it seem likely that these will be addressed?

    Given what the problem is, I'm thinking that I might be able to use FillBehavior=Stop, and then figure out a way to trigger a secondary animation that sets the value back to 0, or (horrible as it sounds) have another element that is the same size, using a visual brush to capture what is on the real element, and animate the "copy" then snap the real one in just before the stop reverts it.


    Again, I appreciate the info and all your time,

    Arlen Feldman
    Friday, August 24, 2007 10:37 PM
  • It's very unlikely these will be addressed for Orcas (WPF 3.5).  The bar is extremely high.  For the next release, there's a better chance.  The first bug is on the data team (i.e. me), so I'm pretty sure it'll get attention.  The second bug is on the animation team, so I can't speak to what will happen.  I'll do what I can to convince them...

     

    Friday, August 24, 2007 11:25 PM