• 0

[C#/WPF] Animate a UserControl


Question

Hello gang,

I am learning WPF and I need to animate a UserControl that has been added during runtime

XAML


<Window x:Class="BediaNVMain.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="515" Width="1598" ShowInTaskbar="False" Topmost="True" WindowState="Maximized" WindowStyle="None" Background="#FF001900" xmlns:my="clr-namespace:BediaMenu;assembly=BediaMenu">
<Canvas Name="BediaCanvas"
<Grid Name="BediaGrid" Loaded="BediaGrid_Loaded">
</Grid>
</Canvas>
</Window>
[/CODE]

C# code to add UserControl (BediaMenu.BMenu)

[CODE]
var bmnuBediaMenu = new BediaMenu.BMenu();
bmnuBediaMenu.Margin = new Thickness(40, MenuTop, 40, 20);
bmnuBediaMenu.MenuText = MenuTitle;
bmnuBediaMenu.Height = this.MenuSize;
bmnuBediaMenu.Width = this.ActualWidth - 80;
this.BediaGrid.Children.Add(bmnuBediaMenu);
[/CODE]

I attempted to use the code from this page: http://stackoverflow.com/questions/4214155/wpf-easiest-way-to-move-image-to-x-y-programmatically But when it gets to the last line; story.Begin(this); it errors with this statement: "'BediaMenu' name cannot be found in the name scope of 'BediaNVMain.MainWindow'." I have attempted several options... but with no success.

Any thoughts?

Link to comment
https://www.neowin.net/forum/topic/1051139-cwpf-animate-a-usercontrol/
Share on other sites

Recommended Posts

  • 0
  On 13/01/2012 at 17:25, ~Johnny said:

It looks like you're not actually telling it to look for this instance of the object - you should be passing in the variable name of the object instead (in this case, bmnuBediaMenu), not BediaMenu.

I am using bmnuBediaMenu... I should have included the code that I affected from that url. So here it is (feel free to slap me and tell me; "NO dumbass, it's.....)


Storyboard story = new Storyboard();
DoubleAnimation dbWidth = new DoubleAnimation();
dbWidth.From = bmnuBediaMenu.Width;
dbWidth.To = 600;
dbWidth.Duration = new Duration(TimeSpan.FromSeconds(2.25));
DoubleAnimation dbHeight = new DoubleAnimation();
dbHeight.From = bmnuBediaMenu.Height;
dbHeight.To = 400;
dbHeight.Duration = dbWidth.Duration;
story.Children.Add(dbWidth);
Storyboard.SetTargetName(dbWidth, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbWidth, new PropertyPath(MediaElement.WidthProperty));
story.Children.Add(dbHeight);
Storyboard.SetTargetName(dbHeight, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbHeight, new PropertyPath(MediaElement.HeightProperty));
DoubleAnimation dbCanvasX = new DoubleAnimation();
dbCanvasX.From = 0;
dbCanvasX.To = 5;
dbCanvasX.Duration = new Duration(TimeSpan.FromSeconds(.25));
DoubleAnimation dbCanvasY = new DoubleAnimation();
dbCanvasY.From = 0;
dbCanvasY.To = 5;
dbCanvasY.Duration = dbCanvasX.Duration;
story.Children.Add(dbCanvasX);
Storyboard.SetTargetName(dbCanvasX, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbCanvasX, new PropertyPath(Canvas.LeftProperty));
story.Children.Add(dbCanvasY);
Storyboard.SetTargetName(dbCanvasY, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbCanvasY, new PropertyPath(Canvas.TopProperty));
story.Begin(this);
[/CODE]

  • 0
  On 13/01/2012 at 17:32, ~Johnny said:

You haven't given your menu a name? Set bmnuBediaMenu.Name to something (You're trying to set a storyboard target to this name, but it hasn't got one)

You were correct, I did not give it a name. (now called "FirstMenu") However adding a name did not affect the error. I now get: 'FirstMenu' name cannot be found in the name scope of 'BediaNVMain.MainWindow'.

I FEEL like it has something to do with the Storyboard not properly linking into the location of bmnuBediaMenu... but I do not really know.

btw: Thanks for your time.

I thought I should post the entire function, just in case


private void LoadBediaMenu(string MenuTitle, double MenuTop)
{
try
{
var bmnuBediaMenu = new BediaMenu.BMenu();

bmnuBediaMenu.Margin = new Thickness(40, MenuTop, 40, 20);
bmnuBediaMenu.Name = "FirstMenu";
bmnuBediaMenu.MenuText = MenuTitle;
bmnuBediaMenu.Height = this.MenuSize;
bmnuBediaMenu.Width = this.ActualWidth - 80;
this.BediaGrid.Children.Add(bmnuBediaMenu);


Storyboard story = new Storyboard();
DoubleAnimation dbWidth = new DoubleAnimation();
dbWidth.From = bmnuBediaMenu.Width;
dbWidth.To = 600;
dbWidth.Duration = new Duration(TimeSpan.FromSeconds(2.25));
DoubleAnimation dbHeight = new DoubleAnimation();
dbHeight.From = bmnuBediaMenu.Height;
dbHeight.To = 400;
dbHeight.Duration = dbWidth.Duration;
story.Children.Add(dbWidth);
Storyboard.SetTargetName(dbWidth, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbWidth, new PropertyPath(MediaElement.WidthProperty));
story.Children.Add(dbHeight);
Storyboard.SetTargetName(dbHeight, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbHeight, new PropertyPath(MediaElement.HeightProperty));
DoubleAnimation dbCanvasX = new DoubleAnimation();
dbCanvasX.From = 0;
dbCanvasX.To = 5;
dbCanvasX.Duration = new Duration(TimeSpan.FromSeconds(.25));
DoubleAnimation dbCanvasY = new DoubleAnimation();
dbCanvasY.From = 0;
dbCanvasY.To = 5;
dbCanvasY.Duration = dbCanvasX.Duration;
story.Children.Add(dbCanvasX);
Storyboard.SetTargetName(dbCanvasX, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbCanvasX, new PropertyPath(Canvas.LeftProperty));
story.Children.Add(dbCanvasY);
Storyboard.SetTargetName(dbCanvasY, bmnuBediaMenu.Name);
Storyboard.SetTargetProperty(dbCanvasY, new PropertyPath(Canvas.TopProperty));
story.Begin(this);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
[/CODE]

  • 0
  On 13/01/2012 at 17:45, articuno1au said:

this.BediaGrid.Children.Add(bmnuBediaMenu);

I think you need to look another layer deeper.

EDIT:: It's entirely possible I am misreading this. I'm struggling with your var names D:

Do you mean, for example, this line


Storyboard.SetTargetName(dbCanvasX, bmnuBediaMenu.Name);
[/CODE]

Should be like this:

[CODE]
Storyboard.SetTargetName(dbCanvasX, this.BediaGrid.bmnuBediaMenu.Name);
[/CODE]

The problem with that is the code cannot find the bmnuBediaMenu as during compile time it is not within the Grid.

  • 0

Yeah, you will need to obtain it with something like this.

BediaMenu temp = new BediaMenu();
temp = BediaGrid.GetChild(<type/name>);[/CODE]

Sorry, it's pretty shoddy pseudocode.

Then operate on temp. That's the best way to do it I can think of.

Is there any reason you are adding it programatically instead of "hard coding" it?

  • 0
  On 13/01/2012 at 17:51, articuno1au said:

Yeah, you will need to obtain it with something like this.

BediaMenu temp = new BediaMenu();
temp = BediaGrid.GetChild(<type name="">);[/CODE]

Sorry, it's pretty shoddy pseudocode.

Then operate on temp. That's the best way to do it I can think of.

Is there any reason you are adding it programatically instead of "hard coding" it?

I will look into getting the object reference (thanks, no worries about the pcode)

The reason that the menus are loaded programatically as opposed to in the XAML is that the number of menus varies based on the user's screen height and the height of the menu (which can be changed by the user) If you want to see where this is going you can see version 1 of Bedia at my site http://www.blissgig.com/display.aspx?id=13

  • 0

Looks interesting..

God damn @ the new IPB text editor. It keeps changing code bits into stupid things D:

The only painful thing about working with programatically generated interfaces is you have to .GetObject/Child/BananaPeel to do anything.

EDIT:: Congrats on evolving from VB.net to C# :p You feel more manly already don't you? :p

  • 0
  On 13/01/2012 at 18:00, articuno1au said:

EDIT:: Congrats on evolving from VB.net to C# :p You feel more manly already don't you? :p

I starting playing with c# back in 2001, but since all my jobs since then have been with VB there has not been a big deal to change over. I tend to be tech-agnostic. Whatever offers the better job, that's what I care about. There are some things I miss about VB, ie: the Intelisense does not work as well.. but nbd.

I tried this code, but still the same error ('FirstMenu' name cannot be found in the name scope of 'BediaNVMain.MainWindow'.) What dumba$$ thing am I doing?


private void LoadBediaMenu(string MenuTitle, double MenuTop)
{
try
{
var bmnuBediaMenu = new BediaMenu.BMenu();

bmnuBediaMenu.Margin = new Thickness(40, MenuTop, 40, 20);
bmnuBediaMenu.Name = "FirstMenu";
bmnuBediaMenu.MenuText = MenuTitle;
bmnuBediaMenu.Height = this.MenuSize;
bmnuBediaMenu.Width = this.ActualWidth - 80;
this.BediaGrid.Children.Add(bmnuBediaMenu);

foreach (UIElement element in this.BediaCanvas.Children)
{
if (element is Grid)
{
Grid grd = element as Grid;
foreach (UIElement mnuElement in grd.Children)
{
if (mnuElement is BediaMenu.BMenu)
{
BediaMenu.BMenu bMenu = mnuElement as BediaMenu.BMenu;
if (bMenu.Name == "FirstMenu")
{
//Animation Test 02
Storyboard story = new Storyboard();
DoubleAnimation dbWidth = new DoubleAnimation();
dbWidth.From = bMenu.Width;
dbWidth.To = 600;
dbWidth.Duration = new Duration(TimeSpan.FromSeconds(2.25));
DoubleAnimation dbHeight = new DoubleAnimation();
dbHeight.From = bMenu.Height;
dbHeight.To = 400;
dbHeight.Duration = dbWidth.Duration;
story.Children.Add(dbWidth);
Storyboard.SetTargetName(dbWidth, bMenu.Name);
Storyboard.SetTargetProperty(dbWidth, new PropertyPath(MediaElement.WidthProperty));
story.Children.Add(dbHeight);
Storyboard.SetTargetName(dbHeight, bMenu.Name);
Storyboard.SetTargetProperty(dbHeight, new PropertyPath(MediaElement.HeightProperty));
DoubleAnimation dbCanvasX = new DoubleAnimation();
dbCanvasX.From = 0;
dbCanvasX.To = 5;
dbCanvasX.Duration = new Duration(TimeSpan.FromSeconds(2.25));
DoubleAnimation dbCanvasY = new DoubleAnimation();
dbCanvasY.From = 0;
dbCanvasY.To = 5;
dbCanvasY.Duration = dbCanvasX.Duration;
story.Children.Add(dbCanvasX);
Storyboard.SetTargetName(dbCanvasX, bMenu.Name);
Storyboard.SetTargetProperty(dbCanvasX, new PropertyPath(Canvas.LeftProperty));
story.Children.Add(dbCanvasY);
Storyboard.SetTargetName(dbCanvasY, bMenu.Name);
Storyboard.SetTargetProperty(dbCanvasY, new PropertyPath(Canvas.TopProperty));
story.Begin(this);
}

}
}
}
}

}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
[/CODE]

  • 0

Sorry, at this point my advice is to put a break point in and see if the code is getting hit.

I can't see anything obvious >.<

EDIT:: I'd also start iterating children as of:


Grid grd = element as Grid;
foreach (UIElement mnuElement in grd.Children)
[/CODE]

Just in case there is some of odd "Typing" issue.

  • 0

I'm not entirely sure, but try changing story.Begin(this) to just story.Begin();

It shouldn't matter whether it's there at compile time or not - by the time you run the code it should already be in the visual tree at the correct position, so it should make no difference (although depending on the hardware and complexity of the menu, you may want to attach the storyboard creation to the loaded event of the UserControl, just to be safe and make sure it's properly in the visual tree)

  • 0
  On 13/01/2012 at 18:21, articuno1au said:

Sorry, at this point my advice is to put a break point in and see if the code is getting hit.

I can't see anything obvious >.<

EDIT:: I'd also start iterating children as of:


Grid grd = element as Grid;
foreach (UIElement mnuElement in grd.Children)
[/CODE]

Just in case there is some of odd "Typing" issue.

I step though the entire function, and it runs fine, finds the correct Menu, I have a titlebar menu already set, so the code correctly ignores that.

  On 13/01/2012 at 18:22, ~Johnny said:

I'm not entirely sure, but try changing story.Begin(this) to just story.Begin();

It shouldn't matter whether it's there at compile time or not - by the time you run the code it should already be in the visual tree at the correct position, so it should make no difference

I tried that, similar error; "No applicable name scope exists to resolve the name 'FirstMenu'."

The issue with compile time is that if I use

[CODE]
Storyboard.SetTargetName(dbCanvasX, this.BediaGrid.bmnuBediaMenu.Name);
[/CODE]

The code will not run/compile as the object "bmnuBediaMenu" does not exist within the grid. Or am I missing what you are trying to say?

Again, thank you both for your time. Happy Friday the 13th!

VS screams

  • 0

If the menu successfully gets added to the Grid without crashing (this is without the storyboard code), then everything should be fine. You are adding the menu to your grid in your code, so it will exist in the visual tree when you run the code.

For what it's worth I've used this code in my own projects to some success for animating across X axis (and applicable for Y)


/// <summary>
/// Slide animate a FrameworkElement along the X axis
/// </summary>
/// <param name="x">The FrameworkElement to animate</param>
/// <param name="df">Starting X offset of the animation</param>
/// <param name="dt">Ending X offset of the animation</param>
/// <param name="time">The length of the animation in milliseconds</param>
internal static void AnimateElementTransformX(FrameworkElement x, Double df, Double dt, Int32 time)
{
try
{
DoubleAnimation ani = new DoubleAnimation();
ani.From = (!(Double.IsNaN(df))) ? df : x.RenderTransform.Value.OffsetX;
ani.To = 0;
ani.Duration = new Duration(TimeSpan.FromMilliseconds(time));
var sb = new Storyboard();
sb.Children.Add(ani);
TranslateTransform tt2 = new TranslateTransform();
App.Current.MainWindow.RegisterName("TT2", tt2);
x.RenderTransform = tt2;
Storyboard.SetTargetName(ani, "TT2");
Storyboard.SetTargetProperty(ani, new PropertyPath(TranslateTransform.XProperty));
x.Resources.Add("SB);
sb.Begin();
x.Resources.Remove("SB");
App.Current.MainWindow.UnregisterName("TT2");
}
catch { /** Null Target Animation Error **/ }
}
[/CODE]

Which you'd use something like

[font=courier new,courier,monospace]AnimateElementTransformX(bmnuBediaMenu, 50, 0, 100);[/font]

for a 100ms animation, animating it from a 50px offset to it's original position

Though I'd probably find it easier to create the animation in XAML in the UserControl, and then set the animation to trigger when the UserControl is loaded.

  • 0

The menu adds to the canvas/grid fine... and I had tried a some code similar to yours (thanks!) but it still errors on sb.Begin() with: 'TT2' name cannot be found in the name scope of 'BediaMenu.BMenu'." I changed the bmnuBediaMenu.Name = "TT2" in the original code to insure that that was not the issue and yet....

I feel that the answer is really close, but I am missing it. (obviously)

  • 0

Ahh, I think I might have spotted it.

Is it that you are calling story.Begin(this) on LoadBediaMenu rather than LoadedBediaMenu (OnLoad/OnLoaded)?

I can't tell where you have registered the event, but that could do it I think >.<

  • 0
  On 13/01/2012 at 18:45, James Rose said:

The menu adds to the canvas/grid fine... and I had tried a some code similar to yours (thanks!) but it still errors on sb.Begin() with: 'TT2' name cannot be found in the name scope of 'BediaMenu.BMenu'." I changed the bmnuBediaMenu.Name = "TT2" in the original code to insure that that was not the issue and yet....

I feel that the answer is really close, but I am missing it. (obviously)

I *think* (I'm not sure, can't remember what I was thinking when I wrote it :p), but this *might* work for you.


/// <summary>
/// Slide animate a FrameworkElement along the X axis
/// </summary>
/// <param name="x">The FrameworkElement to animate</param>
/// <param name="df">Starting X offset of the animation</param>
/// <param name="dt">Ending X offset of the animation</param>
/// <param name="time">The length of the animation</param>
/// <param name="p">The parent element to register the transform in</param>
internal static void AnimateElementTransformX(FrameworkElement x, Double df, Double dt, Int32 time, FrameworkElement p)
{
try
{
DoubleAnimation ani = new DoubleAnimation();
ani.From = (!(Double.IsNaN(df))) ? df : x.RenderTransform.Value.OffsetX;
ani.To = 0;
ani.Duration = new Duration(TimeSpan.FromMilliseconds(time));
var sb = new Storyboard();
sb.Children.Add(ani);
TranslateTransform tt2 = new TranslateTransform();
p.RegisterName("TT2", tt2);
x.RenderTransform = tt2;
Storyboard.SetTargetName(ani, "TT2");
Storyboard.SetTargetProperty(ani, new PropertyPath(TranslateTransform.XProperty));
x.Resources.Add("SB);
sb.Begin();
x.Resources.Remove("SB");
p.UnregisterName("TT2");
}
catch { /** Null Target Animation Error **/ }
}
[/CODE]

The difference here is you also have to pass in the element's parent element at the end as well, so something like AnimateElementTransformX(bmnuBediaMenu, 50, 0, 100, BediaGrid) might work. Although for some reason I never actaully used this code over the previous method, so it may not actually work. (Again, I must prefer to write them in XAML)

  • 0

Johnny,

Thanks, and that was what I was attempting to do (had not gotten there yet, so thanks!) but still an error on sb.Begin

'TT2' name cannot be found in the name scope of 'BediaMenu.BMenu'.

-----------------

Art,

The function "LoadBediaMenu" adds one menu and then calls Johnny's function "AnimateElementTransformX" where the BediaMenu is to be moved. Is this an issue? Can't I add a control and then move it within the same function? (even though "technically" these are two functions)

  • 0

I think it's to do with the object not being fully initiated.

I was just working my way through: http://msdn.microsoft.com/en-us/library/ms752312.aspx

They've bound the animation separately from the animation start. This is (I think) irrelevant. The interesting thing to note is when the story.Begin(this) is called. I think you need to delay the Begin call until after the object is fully loaded.

I think the best part of my idea is all it "costs" is binding the start to Loaded rather than Load. 2 seconds to test, 2 seconds to undo >.<

  • 0
  On 13/01/2012 at 19:24, articuno1au said:

I think it's to do with the object not being fully initiated.

I was just working my way through: http://msdn.microsof...y/ms752312.aspx

They've bound the animation separately from the animation start. This is (I think) irrelevant. The interesting thing to note is when the story.Begin(this) is called. I think you need to delay the Begin call until after the object is fully loaded.

I think the best part of my idea is all it "costs" is binding the start to Loaded rather than Load. 2 seconds to test, 2 seconds to undo >.<

I believe you're taking about this piece from that link


private void myRectangleLoaded(object sender, RoutedEventArgs e)
{
myStoryboard.Begin(this);
}
[/CODE]

I have been reviewing that page for a few days and saw that. However it does not matter. For example I have this code that is called when I click on the Titlebar

[CODE]
private void Titlebar_MouseDown(object sender, MouseButtonEventArgs e)
{
if (bLoaded == false)
{
LoadBediaMenu("Fade In and Out 2", 500);
bLoaded = true;
}
else
{
AnimateElementTransformX(bmnuBediaMenu, 0, 30, 100, BediaGrid);
}
}
[/CODE]

The BediaMenu is completely loaded and yet I still get the same error: 'TT2' name cannot be found in the name scope of 'BediaMenu.BMenu'.

Very strange, very interesting.... and more than a bit annoying. One of my laws of development is that the longer it takes to find a solution to an issue, the more simple/straight-forward it is, and therefore the dumber you will feel when you figure it out. This one is going to make me feel REAL stupid, I just know it.

  • 0

I'd say skip all that, go with this in the XAML of the UserControl:


<Usercontrol ....
x:name="bmnuMenu"
...>
<UserControl.Resources>
<Storyboard x:Key="Entry">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="bmnuMenu">
<EasingDoubleKeyFrame KeyTime="0" Value="30"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</UserControl.RenderTransform>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Entry}"/>
</EventTrigger>
</UserControl.Triggers>[/CODE]

You probably don't need all those transform groups, but that should work.

Edit: Stupid thing has messed up the casings.

  • 0
  On 13/01/2012 at 19:42, ~Johnny said:

I'd say skip all that, go with this in the XAML of the UserControl:


<usercontrol ....="" x:name="UserControl">
<usercontrol.resources>
<storyboard x:key="Entry">
<doubleanimationusingkeyframes storyboard.targetproperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" storyboard.targetname="UserControl">
<easingdoublekeyframe keytime="0" value="30">
<easingdoublekeyframe keytime="0:0:0.3" value="0">
</easingdoublekeyframe></easingdoublekeyframe></doubleanimationusingkeyframes>
</storyboard>
</usercontrol.resources>
<usercontrol.rendertransform>
<transformgroup>
<scaletransform>
<skewtransform>
<rotatetransform>
<translatetransform>
</translatetransform></rotatetransform></skewtransform></scaletransform></transformgroup>
</usercontrol.rendertransform>
<usercontrol.triggers>
<eventtrigger routedevent="FrameworkElement.Loaded">
<beginstoryboard storyboard="{StaticResource Entry}">
</beginstoryboard></eventtrigger>
</usercontrol.triggers>
[/CODE]

You probably don't need all those transform groups, but that should work.

I'd love to just use XAML, but the number of menus varies based on the user's screen height and the height of the menu (customizable) so until runtime I do not know how many I will need. I REALLY wish I could know ahead of time, that would make my life SO much easier.

This topic is now closed to further replies.
  • Recently Browsing   0 members

    • No registered users viewing this page.