Today I'm dealing with the problem of creating an animation for components that are created in template.
Previously I had something like this (implemented in TextBlock onl oad Trigger):
<TextBlock.Triggers>
<EventTrigger RoutedEvent="TextBlock.Loaded">
<BeginStoryboard>
<Storyboard
x:Name="contentStoryboard"
Storyboard.TargetName="contentText">
<DoubleAnimation
BeginTime="0:0:0"
Storyboard.TargetProperty="(Canvas.Left)"
AutoReverse="{Binding MarqueeBouncing, RelativeSource={RelativeSource AncestorType={x:Type local:MarqueeTextBlockEx}}}"
Duration="{Binding MarqueeDuration, RelativeSource={RelativeSource AncestorType={x:Type local:MarqueeTextBlockEx}}}"
RepeatBehavior="Forever">
<DoubleAnimation.From>
<MultiBinding Converter="{StaticResource StartPositionConverter}">
<Binding Path="MarqueeStartPosition" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MarqueeTextBlockEx}}"/>
<Binding ElementName="border" Path="ActualWidth"/>
<Binding ElementName="contentText" Path="ActualWidth"/>
<Binding Path="WaitForText" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MarqueeTextBlockEx}}"/>
</MultiBinding>
</DoubleAnimation.From>
<DoubleAnimation.To>
<MultiBinding Converter="{StaticResource EndPositionConverter}">
<Binding Path="MarqueeEndPosition" RelativeSource="{RelativeSource AncestorType={x:Type local:MarqueeTextBlockEx}}"/>
<Binding ElementName="border" Path="ActualWidth"/>
<Binding ElementName="contentText" Path="ActualWidth"/>
<Binding Path="WaitForText" RelativeSource="{RelativeSource AncestorType={x:Type local:MarqueeTextBlockEx}}"/>
</MultiBinding>
</DoubleAnimation.To>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
And after I wanted to implement bool parameter to control animation on and off. I created a code behind:
public MarqueeTextBlockEx()
{
Loaded = onl oaded;
}
static MarqueeTextBlockEx()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MarqueeTextBlockEx),
new FrameworkPropertyMetadata(typeof(MarqueeTextBlockEx)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
contentBorder = GetBorder("border");
contentCanvas = GetCanvas("contentCanvas");
contentTextBlock = GetTextBlock("contentText");
}
protected TextBlock GetTextBlock(string textBlockName)
{
return this.Template.FindName(textBlockName, this) as TextBlock;
}
protected virtual void onl oaded(object sender, RoutedEventArgs e)
{
Storyboard = CreateStoryboard();
if (MarqueeEnabled)
Storyboard.Begin();
}
private Storyboard CreateStoryboard()
{
var storyboard = new Storyboard();
var doubleAnimation = new DoubleAnimation()
{
AutoReverse = MarqueeBouncing,
BeginTime = new TimeSpan(0, 0, 0),
Duration = MarqueeDuration,
RepeatBehavior = RepeatBehavior.Forever,
From = GetStartPosition(),
To = GetEndPosition()
};
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("Canvas.Left"));
Storyboard.SetTarget(doubleAnimation, contentTextBlock);
return storyboard;
}
private double GetStartPosition()
{
var startPosition = MarqueeStartPosition;
var canvasWidth = contentBorder.ActualWidth;
var textWidth = contentTextBlock.ActualWidth;
var waitForText = WaitForText;
switch (MarqueeStartPosition)
{
case MarqueeTextAnimationPlace.LeftOutside:
return -textWidth;
case MarqueeTextAnimationPlace.LeftInside:
return waitForText && IsTextTooLong(canvasWidth, textWidth)
? -(textWidth - canvasWidth)
: 0;
case MarqueeTextAnimationPlace.RightInside:
return waitForText && IsTextTooLong(canvasWidth, textWidth)
? 0
: canvasWidth - textWidth;
case MarqueeTextAnimationPlace.RightOutside:
default:
return canvasWidth;
}
}
private double GetEndPosition()
{
var startPosition = MarqueeEndPosition;
var canvasWidth = contentBorder.ActualWidth;
var textWidth = contentTextBlock.ActualWidth;
var waitForText = WaitForText;
switch (startPosition)
{
case MarqueeTextAnimationPlace.LeftOutside:
return -textWidth;
case MarqueeTextAnimationPlace.LeftInside:
return waitForText && IsTextTooLong(canvasWidth, textWidth)
? -(textWidth - canvasWidth)
: 0;
case MarqueeTextAnimationPlace.RightInside:
return waitForText && IsTextTooLong(canvasWidth, textWidth)
? 0
: canvasWidth - textWidth;
case MarqueeTextAnimationPlace.RightOutside:
default:
return canvasWidth;
}
}
// And of course the property for control:
public bool MarqueeEnabled
{
get => marqueeEnabled;
set
{
marqueeEnabled = value;
if (Storyboard != null)
{
if (value)
Storyboard.Begin();
else
{
Storyboard.Stop();
TextPosition = 0;
}
}
}
}
The TextBlock component is setup in this way
<Border
x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid
x:Name="grid"
Margin="{TemplateBinding Padding}">
<Canvas
x:Name="contentCanvas"
ClipToBounds="True"
Height="{Binding ActualHeight, ElementName=contentText}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="{Binding ActualWidth, ElementName=grid}">
<TextBlock
x:Name="contentText"
Canvas.Left="{Binding TextPosition, RelativeSource={RelativeSource AncestorType={x:Type local:MarqueeTextBlockEx}}}"
Foreground="{TemplateBinding Foreground}"
Height="Auto"
HorizontalAlignment="Left"
Text="{TemplateBinding Text}"
VerticalAlignment="Center"
Width="Auto"/>
</Canvas>
</Grid>
</Border>
What could have caused it not to work that way?
CodePudding user response:
Add the DoubleAnimation
to the Children
property of the Storyboard
and add parentheses around the Canvas.Left
:
var storyboard = new Storyboard();
var doubleAnimation = new DoubleAnimation()
{
...
};
storyboard.Children.Add(doubleAnimation);
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Left)"));
Storyboard.SetTarget(doubleAnimation, contentTextBlock);
storyboard.Begin();
If you still cannot me it work, then please edit your question to include a minimal and reproducible Example.