Storyboard inside a user control is not working - c#

I am developing a Windows 8 Store App. I have created the following user control:
<UserControl
x:Class="RTV_W8.AnimatedImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RTV_W8"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<!-- Animates the rectangle's opacity. -->
<Storyboard x:Name="ContainerGridStoryboard">
<DoubleAnimation
Storyboard.TargetName="MainGrid"
Storyboard.TargetProperty="Opacity"
From="0" To="1" Duration="0:0:5"
AutoReverse="False">
</DoubleAnimation>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="MainGrid" CacheMode="BitmapCache">
<Image Stretch="UniformToFill" x:Name="PictureImage"/>
</Grid>
</UserControl>
with the following cs file (only partially displayed here)
public AnimatedImage()
{
this.InitializeComponent();
this.Loaded += AnimatedImage_Loaded;
this.PictureImage.Loaded += PictureImage_Loaded;
}
void AnimatedImage_Loaded(object sender, RoutedEventArgs e)
{
this.ContainerGridStoryboard.Begin();
}
my problem is the storyboard is not launching when the image is loaded. This user control is used inside another usercontrol. I have tried multiple variants trying to make the animation to work, none of them worked. I want the user control to animate it's opacity into view, instead of just pooping up. Although i can see via breakpoints that the ContainerGridStoryboard.Begin(); is executed nothing happens on screen
EDIT: In another usercontrol witch is later used in a datatemplate, if i apply the storyboard on the main grid it works, the usercontrol gets animated. But if i apply it on the second grid (contained in the main grid) or any other element, the animation does not work. That is why i created animatedimage in the first place.

I have just solved the problem. So my usercontrol was inside a couple of grids, inside another usercontrol, inside a datatemplate inside a gridview. The problem was that the main grid witch contained the animatedimage was set with CacheMode="BitmapCache". When i removed that property the images faded in.

When I set the image's source, and embed the UserControl into a page, the image fades in as expected.

Related

WPF Child Control Inheritance

I am trying to implement a control to inherit from in WPF.
I have never been working with WPF (at least at that level though).
So I need some direction of best practice on how to solve this.
The problem I´m facing is that my control, that I want to inherit from, has some child controls that need to be accessed inside the controls base class.
I want to reuse that control with these child controls inside, because it has functions to fill the child controls from outside.
But since WPF can´t inherit a control with xaml, I can´t get my head around a solution.
Let´s say I have this control.
<StackPanel x:Class="Framework.UI.Controls.Base.Navigator.NavigatorItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Framework.UI.Controls.Base.Navigator"
mc:Ignorable="d"
d:DesignHeight="26" d:DesignWidth="200">
<Button Name="btnHeader" Content="Button"/>
<TreeView Name="tvNavContent" Height="0"/>
</StackPanel>
In codebehind the Button is being used for a Click event as well as the header Text, which I want to be filled from the Control that inherits from this.
And with a function the TreeView "tvNavContent" is being filled with something like this:
<TreeViewItem x:Class="Framework.UI.Controls.Base.Navigator.NavigatorEntry"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Framework.UI.Controls.Base.Navigator"
mc:Ignorable="d"
d:DesignHeight="20" d:DesignWidth="200">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Name="imgIcon" Width="16" Height="16" Stretch="Fill"/>
<TextBlock Name="txtTitle"/>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
What I want to achieve is to reuse the Stackpanel with the Button and TreeView inside and with it´s functions.
I tried two things:
First I tried to create a template and applied that to the base class. After that I just tried to load the controls of the template in the base class with the FindName<>() function.
The problem here is, that the template is applied after InitializeComponent().
But during InitializeComponent() I already need access, to set the controls header property for the title from the control that inherits from the base class.
After that I tried to implement the child controls completely in the base class of the control.
Just created them in the constructor and added them to the Children Property of the stackpanel the base class inherits from.
That did (somewhat) work.
But apparently the controls behave completely different when created like that.
No matter the settings. I just couldn´t get the controls to fit correctly inside their parents.
Furthermore, this method is completely unsuitable for a larger project, when it comes to theme adjustments.
Can someone guide me in the correct direction here?
Create a class called NavigatorItem (without any .xaml file):
public class NavigatorItem : Control
{
static NavigatorItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NavigatorItem),
new FrameworkPropertyMetadata(typeof(NavigatorItem)));
}
}
Create a ResourceDictionary called generic.xaml and put it in a folder called themes (these names are by convention) at the root of your project, and define a default template for the NavigatorItem class in there:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp12">
<Style TargetType="local:NavigatorItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NavigatorItem">
<StackPanel>
<Button Name="btnHeader" Content="Button"/>
<TreeView Name="tvNavContent" Height="0"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
You can then override the OnApplyTemplate of the NavigatorItem class to get a reference to the elements in the template and hook up event handlers to them, e.g.:
public override void OnApplyTemplate()
{
Button button = GetTemplateChild("btnHeader") as Button;
button.Click += Button_Click;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("button clicked!");
}

Loading a grid in xaml code from a file, then making it active in main code

I am making a video game, where each grid serves as game room. Each grid\room has a large number of objects like things that can be used, images and sound files. Until now, they all were stored in one large file. But now, I was told that this approach wastes a lot of resources.
My plan is, to store xaml code of every such grid as a separate file, then load relevant file at run time with usual code.
For now xaml looks about like this:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" x:Name="wdwMain" x:Class="RealityIncognita.MainWindow"
Height="900" Width="1600" ResizeMode="NoResize" WindowState="Maximized"
Cursor="Cross" WindowStyle="None" Loaded="wdwMain_Loaded">
<Viewbox Stretch="Fill">
<Grid x:Name="areaContainer" HorizontalAlignment="Left" Height="900"
VerticalAlignment="Top" Width="1600">
<Grid x:Name="areaMain">
<Grid.Background>
<ImageBrush
ImageSource="Resources/Images/Interface/main_interface.jpg"/>
</Grid.Background>
<Grid x:Name="areaShowers" HorizontalAlignment="Left" Height="700"
Margin="1653,790,-1561,-590" VerticalAlignment="Top" Width="1508"
IsVisibleChanged="areaShowers_IsVisibleChanged">
Here's example - areaShowers is a grid for relevant room. Until now, it was stored in the main file, like all other grids, and when I needed, I just altered its Margin to put it upon "areaMain" - also a grid.
Now though, I want to put each room into a file, then load it when I need it, and remove it from memory when I don't.
For example I'll create an empty grid "areaGeneric" and add it and it alone to original xaml.
So, I want something like this. Can't provide any earlier attempt, because I don't really know how it can be done.
Grid new_grid = new Grid;
new_grid = load from file (areaRoom.xaml); (file is among the project's
resources)
areaGeneric = new_grid;
Can I load a grid xaml at run-time, then switch grids in main code?
Thank you,
Evgenie
A really simple solution would just be to house each of your rooms inside a user control and then place the control into your container grid when you need to change rooms. Here's the rough idea:
public partial class MainWindow : Window
{
UserControl _currentRoom;
public MainWindow()
{
InitializeComponent();
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
this.areaContainer.Children.Clear();
_currentRoom = null;
if (e.LeftButton == MouseButtonState.Pressed)
_currentRoom = new Room1();
if(e.RightButton == MouseButtonState.Pressed)
_currentRoom = new Room2();
this.areaContainer.Children.Add(_currentRoom);
base.OnPreviewMouseDown(e);
}
}
A "Room":
<UserControl x:Class="Test.Room1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Button Height="100" Width="100">
Hello world!
</Button>
</UserControl>
As for cleaning up your resources - once the user control is removed from the visual tree (assuming you aren't holding a reference) the GC should dispose of the user control. If you wanted to clear your resources more quickly you could implement a dispose method on your rooms and then call that before you change areas.

WPF DispatcherTimer on Modalless window

I have a wpf progress window defined as following:
<Window x:Class="NeoinfoXmlEditor.WPF.Forms.ProgressDisplayForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="84" Width="505" x:Name="root" WindowStartupLocation="CenterScreen">
<Grid>
<ProgressBar Height="15" x:Name="MessageProgessBar" HorizontalAlignment="Stretch" VerticalAlignment="Top" Maximum="10000" Margin="10,2,10,2" >
<ProgressBar.Triggers>
<EventTrigger RoutedEvent="ProgressBar.Loaded">
<BeginStoryboard>
<Storyboard x:Name="sb">
<DoubleAnimation Storyboard.TargetName="MessageProgessBar"
Storyboard.TargetProperty="Value"
From="0" To="10000" Duration="0:0:45"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ProgressBar.Triggers>
</ProgressBar>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5" Text="{Binding ElementName=root, Path=Message}" />
</Grid>
</Window>
And a code behind file as follows:
public partial class ProgressDisplayForm : Window
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof (string), typeof (ProgressDisplayForm));
public string Message
{
get { return (string) GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public ProgressDisplayForm()
{
InitializeComponent();
}
public void DisplayWindow()
{
this.Show();
this.BeginStoryboard(sb);
}
}
You can see that I try to start a progressBar animation in two ways:
-using EventTrigger, on ProgressBar.Loaded
-from code behind, explicitely
The problem is - neither works.
Note - I need to open this window and start animation as modalless window, so ShowDialog() is not na option. Also, I tried using DispatcherTimer, but it somehow doesn't work, niether the this.Dispatcher.Invoke() while using System.Timers.Timer class.
I'm calling the DisplayWindow() method from the main app window.
What am I missing?
Thanks in advance
I couldn't reproduce your problem, your XAML animation is working just fine!, try to copy your XAML code to a new project without the code-behind. I tried that and worked just fine :D
I found out what the problem was - i called NewWindow.Show(), and then continued with some high CPU computing, assuming that the new window will be on separate thread if not called with ShowDialog().
I fixed it using BackgroundWorker!
Thanks for help anyways!

Architecting...a slideshow

I know this sounds silly and I could use some out-of-the-box solution, but I really want to build my own simple image slideshow. I've been doing application development in Silverlight/WPF for some time, but for whatever reason I can't wrap my head around this.
I have an observable collection of SlideshowItem
Each SlideshowItem has Source which indicates where the image for it is located
I show a translucent box for each SlideshowItem (horizontal list using a stackpanel) and when you click, you should transition to that slide
So here's my problem: If I have that list with a stackpanel template, and under the list is an image taking up the size of the canvas, I can bind the context of the image to the selected SlideshowItem. That's all well and good. But when I click/change the selected index of the list, I want to do a crossfade or slide between two images.
How should I represent this in Silverlight? Should I actually have a scroll panel or something with all the images and then change between them? Or is it sufficient to use a single image control? Can I do this with states, or do I need to explicitly run a storyboard? Any samples would be appreciated.
You can use the TransitioningContentControl from the Silverlight Toolkit, however if you want to roll your own you will need two content controls and swap out the "Active" one on SelectionChanged events. You also can fire your storyboards here.
ContentControl _active;
private void LB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_active == Content1)
{
_active = Content2;
Content2Active.Begin();
} else
{
_active = Content1;
Content1Active.Begin();
}
_active.Content = LB.SelectedItem;
}
And the Xaml looks something like this. I just use strings and text, but this approach should work reasonable well for images too:
<Grid x:Name="LayoutRoot" Background="White" MaxHeight="200">
<Grid.Resources>
<Storyboard x:Name="Content1Active">
<DoubleAnimation From="0" To="1" Storyboard.TargetName="Content1" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Storyboard.TargetName="Content2" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
<Storyboard x:Name="Content2Active">
<DoubleAnimation From="0" To="1" Storyboard.TargetName="Content2" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Storyboard.TargetName="Content1" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</Grid.Resources>
<StackPanel>
<ListBox x:Name="LB" SelectionChanged="LB_SelectionChanged" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>Red</sys:String>
<sys:String>Green</sys:String>
<sys:String>Blue</sys:String>
</ListBox>
<Grid>
<ContentControl x:Name="Content1" FontSize="40" Foreground="{Binding Content, RelativeSource={RelativeSource Self}}">
</ContentControl>
<ContentControl x:Name="Content2" FontSize="40" Foreground="{Binding Content, RelativeSource={RelativeSource Self}}">
</ContentControl>
</Grid>
</StackPanel>
</Grid>
Definitely you don't need the entire Image collection displayed in a scrollviewer/stackpanel. You can implement this in many different ways. I can explain a simple idea of Using one Image : As you said , define a SelectedSlide property in your ViewModel and bind that to an Image control ( Preferably a ContentControl with Image as its part of the ContentTemplate, so that you can have descriptions and other items in the same). This solution can give you the opportunity to add some storyboards so that if you increase your SelectedIndex(Another VM property) fire a storyboard to do a 'Left Move' animation and if you decrease do a 'Right Move' animation makes user feels like slides are coming from one side and going the other way. You can do pretty good UX on that set of storyboards.
Update (Idea 2) : Yes if we need the notion of the previous one leaving the view when new one coming in, we can architect it by using two ContentControls wrapped inside a CustomControl ( lets call it as SlideShowControl). SlideShowControl will have its mechanism to properly set DataContext of the two ContentControl based on the selectedIndex position. I have successfully made this control in one of my projects, the logic here is to switch the ContentControls through a storyboard so that we can have many different effects by swapping the storyboard. Suppose you move from Index 1 to 2, ContentControlA will animate to left, and B will come in to the View, and based on your next click ControlA will go sit either left or right of the View, and comes with new DataContext of the selected View.

How do I start a Storyboard in a Data Template in a Content Control in a User Control from codebehind?

PHEW.
I'm serious. I'll spell it out as follows...
The Storyboard has a Key "myStoryboard". It's held in a DataTemplate with a key "myDataTemplate".
This data template is used in a ContentControl with the name "myContentControl" by this tag:
<ContentControl Name="myContentControl"
ContentTemplate="{DynamicResource myDataTemplate}"/>
The content control is used in my UserControl. In the UserControl codebehind I've made a keyboard gesture that supposed to start "myStoryBoard" but i'm having no such luck getting to it.
private void StartSB(object sender, ExecutedRoutedEventArgs e)
{
Storyboard sb = (Storyboard) this.TryFindResource("myStoryboard");
sb.Begin(this);
}
sb is always null here. How can i get the Storyboard?
UPDATE:
so playing with TryFindResource() I've managed to get to myDataTemplate
private void StartSB(object sender, ExecutedRoutedEventArgs e)
{
object sb = this.myContentControl.TryFindResource("myDataTemplate");
}
in the Locals viewer I can see sb is myDataTemplate. I can see in the tree sb.base.Resources.Keys which is an array of resources in which "myStoryboard" is inside. Oh so close!
UPDATE2:
More code complete here. I realize now this may be too spaghettified to explain with words.
<UserControl >
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\myUCResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<ContentControl Name="myContentControl"
ContentTemplate="{DynamicResource myDataTemplate}"
Content="{Binding}" />
...
</UserControl>
now the codebehind for this User control
namespace myclass
{
public partial class myUserControl: UserControl, System.ComponentModel.INotifyPropertyChanged
{
...
public myUserControl()
{
InitializeComponent();
<!--setup keybinding-->
}
public void KeyBindExecuted(object sender, ExecutedRoutedEventArgs e)
{
Object sb = this.myContentControl.TryFindResource("myDataTemplate");
//sb returns the DataTemplate
}
}
}
And finally the resource dictionary containing the UI stuff and the animation I ultimately want to trigger. (myUCResources.xaml)
<ResourceDictionary>
<DataTemplate x:Key="myDataTemplate" >
<Grid>
<!-- elements like buttons -->
</Grid>
<DataTemplate.Resources>
<Storyboard x:Key="myStoryBoard">
<DoubleAnimation>
<!-- animation stuff-->
</DoubleAnimation>
</Storyboard>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<EventTrigger SourceName="aButton" RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource myStoryBoard}" />
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ResourceDictionary>
Update 3:
ok different approach. Can I use the EventTrigger in the DataTemplate from the codebehind to start the animation?
AH HAH!
So I found a roundabout way to solving this. My third update where I entertained the thought of just firing an event seemed more fruitful. All can be found here.
http://www.codeproject.com/script/Forums/View.aspx?fid=1004114&msg=2827455
In a nutshell I used FindResource to get the DataTemplate, then FindName of the button in the DataTemplate used to normally trigger the animation. Then I raised a button click on that button.

Categories

Resources