I am wondering if there is an easy way to modify some kind of shared resource (i.e. a Brush) of a control between different VisualStates. For example, I would like to define a Brush to use as both the Background of a Border and the Fill of a different Rectangle. In a different VisualState I would like to change this background Brush in one place (the resource) and have it reflected in all elements using the resource.
I am not sure if resources can really be referenced by Name (not Key) for the TargetName of the Storyboard in the VisualState.
Here is a simplified example of what I am trying to do in XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication.MainPage"
Width="200" Height="200">
<UserControl.Resources>
<SolidColorBrush x:Name="Background" x:Key="Background" Color="Black" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MyStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Red">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Color)">
<EasingColorKeyFrame KeyTime="00:00:00" Value="Red"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="{StaticResource Background}" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Red" BorderThickness="1"/>
<Rectangle Fill="{StaticResource Background}" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</UserControl>
I have a feeling because these are StaticResources in Silverlight they are only loaded once and can not be changed. I know WPF has some concept of DynamicResources. Is there any way to achieve this type of behavior in Silverlight without having to redefine my brush in all elements?
DynamicResources unfortunately don't exist in Silverlight.
Some people use Bindings.
There's a sneaky way to simulate the experience within a single UserControl that you might want to try.
All that's happening in the code below is that the Storyboard animates a single rectangle's Fill, which is then bound to the other Fill's in the UserControl by using Element binding. The source rectangle doesn't need to be visible for this to work.
<UserControl x:Class="TestSilverlightStuff.MainPage"
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"
xmlns:local="clr-namespace:TestSilverlightStuff"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<SolidColorBrush x:Key="Background" Color="Black" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MyStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Red">
<Storyboard>
<ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="trickyRectangle" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle Fill="Black" x:Name="trickyRectangle" Visibility="Collapsed" />
<Border Background="{Binding Fill, ElementName=trickyRectangle}" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Red" BorderThickness="1"/>
<Rectangle Fill="{Binding Fill, ElementName=trickyRectangle}" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
<Button Content="Button" Height="57" HorizontalAlignment="Left" Margin="12,231,0,0" Name="button1" VerticalAlignment="Top" Width="153" Click="button1_Click" />
</Grid>
</UserControl>
Here's the C# button click code:
private void button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
VisualStateManager.GoToState(this, "Red", true);
}
It's not as elegant as a DynamicResource, but it works in some cases.
Related
I have a hexagonal Polygon that I am trying to use as an OpacityMask, then have content inside the polygon that can scroll and be clipped at the edges. The problem I encounter is that as the content moves, the OpacityMask moves with it (though by a different amount). Below is my code:
<Window Title="MainWindow" Height="450" Width="800">
<Grid Height="450" Width="800">
<StackPanel>
<Grid ClipToBounds="True">
<Grid.OpacityMask >
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Polygon Points="220,225 310,69 490,69 580,225 490, 381 310, 381" Fill="Black" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.OpacityMask>
<Polygon Points="220,225 310,69 490,69 580,225 490, 381 310, 381" Stroke="Black" StrokeThickness="5"/>
<Rectangle x:Name="TestRectangle" Height="400" Width="400" Fill="Red" />
</Grid>
<Button Content="Testing" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetName="TestRectangle" Storyboard.TargetProperty="Margin"
BeginTime="0:0:0" Duration="0:0:0.5" To="-400,0,0,0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
</Grid>
</Window>
How do I anchor the opacity mask so that the grid content can move with the mask remaining stationary?
Edit: Answer Results and Additional Testing
Using the code in an answer provided, I still get movement of the hexagon:
Begin:
Begin State
End:
End State
However, I have found that by adding a second rectangle that is transparent and that moves in the opposite direction of the red rectangle, I achieve the desired result. While this does work, it seems like there should be a better way to do it.
If you try to set the margin of the rectangle directly, you would notice that it is ignored inside a grid. So one way to solve that would be to put the rectangle inside a border and animate the margin for that border
<Window x:Class="StackverflowTests.MainWindow"
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"
xmlns:local="clr-namespace:StackverflowTests"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Height="450" Width="810">
<StackPanel>
<Grid ClipToBounds="True">
<Grid.OpacityMask >
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Polygon Points="220,225 310,69 490,69 580,225 490, 381 310, 381" Fill="Black" />
</VisualBrush.Visual>
</VisualBrush>
</Grid.OpacityMask>
<Polygon Points="220,225 310,69 490,69 580,225 490, 381 310, 381" Stroke="Black" StrokeThickness="5"/>
<Border x:Name="TestRectangle">
<Rectangle Height="400" Width="400" Fill="#ff0000"/>
</Border>
</Grid>
<Button Content="Testing" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Storyboard.TargetName="TestRectangle" Storyboard.TargetProperty="Margin"
BeginTime="0:0:0" Duration="0:0:0.5" To="-400,0,0,0" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
</Grid>
I have these border and label:
<Border x:Name="PulseBoba" Width="auto" Height="auto" Background="#FFF75959" CornerRadius="2" Margin="0" HorizontalAlignment="Left">
<Label Content="{Binding kolicina}" FontSize="20" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold" Width="Auto" Margin="5,0">
<Label.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Label.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Label.Triggers>
</Label>
</Border>
In my vb.net code i have this piece of code which sets values correctly and displays in label:
...
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Property kolicina() As String
Get
Return m_kolicina
End Get
Set
m_kolicina = Value
NotifyPropertyChanged("kolicina")
End Set
End Property
...
How would i animate this border to blink or rotate or any other animation when value of "kolicina" is changed?
You could for example bind the Tag property of the Border and use an EventTrigger that listens to the Binding.SourceUpdated attached event:
<Border x:Name="PulseBoba" Width="auto" Height="auto" Background="#FFF75959" CornerRadius="2" Margin="0" HorizontalAlignment="Left"
Tag="{Binding kolicina, NotifyOnTargetUpdated=True}">
<Border.RenderTransform>
<RotateTransform Angle="0" />
</Border.RenderTransform>
<Border.Triggers>
<EventTrigger RoutedEvent="Binding.SourceUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Border.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Label Content="{Binding kolicina}" FontSize="20" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="Bold" Width="Auto" Margin="5,0" />
</Border>
If you require more control, you should implement the animation programmtically. You could then for example handle the PropertyChanged event of the view model in the view and create a Storyboard yourself in the code-behind based on any condition. This is a typical example of a case where it makes perfect sense to implement view-related stuff in the view.
I need to place numerous Image controls on an InkCanvas. All of them need to animate when certain code conditions are met. After numerous attempts my ControlTemplate is still not right.
<ControlTemplate x:Key="AnimatedImage" TargetType="{x:Type Image}">
<Grid>
<Image Name="image"
Source="{TemplateBinding Filename}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="AnimationLocations">
<!-- When the image is first loaded move it to the center of the InkCanvas -->
<VisualState Name="AnimateToCenterScreen">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="image"
Storyboard.TargetProperty="(Image.RenderTransform).(TranslateTransform.X)"
From="{TemplateBinding From}"
To="{TemplateBinding To}"
Duration="{TemplateBinding Duration}" />
</Storyboard>
</VisualState>
<!-- Animate the image to an off screen position to effectively hide it. -->
<VisualState Name="AnimateToOffScreen">
<!-- To be filled in later. -->
</VisualState>
<!-- Animate the image back to the original on screen position. -->
<VisualState Name="AnimateToOnScreen">
<!-- To be filled in later. -->
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Image>
</Grid>
</ControlTemplate>
Here is the xaml for the main grid:
<Image Name="image1"
Template="{StaticResource AnimatedImage}"
Source="{Binding Path=Image1}"
Visibility="{Binding ElementName=chkShowPerson, Path=IsChecked, Converter={StaticResource b2v}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" >
</Image>
</InkCanvas>
</Grid>
The VisualStateManager element has to be located as the child of the control template's root.
<ControlTemplate x:Key="AnimatedImage" TargetType="{x:Type Image}">
<Grid>
<VisualStateManager.VisualStateGroups>
...
</VisualStateManager.VisualStateGroups>
<Image Name="image" ... />
</Grid>
</ControlTemplate>
(For reference here is the page on MSDN where you can find that tidbit.)
I have a xaml start up form in which I am trying to host a windows form control which is a splash screen displaying Product Name / License stuff.. Here is the xaml code which I am using to host the user control but its not at all visible. Not in the designer nor when I run the application actually... Whats wrong in this?
<Window
x:Class="StartupWindow"
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"
xmlns:wf="clr-namespace:namespace;assembly=assemblyName"
mc:Ignorable="d" x:Name="splashWindow"
WindowStyle="None" ResizeMode="NoResize" Width="500" Height="400"
AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True"
>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Unloaded">
<BeginStoryboard>
<Storyboard x:Name="board">
<DoubleAnimation Storyboard.TargetName="splashWindow" Storyboard.TargetProperty="Opacity" From="1.0" To="0" Duration="0:0:1.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid x:Name="LayoutRoot">
<Grid x:Name="Splash" Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,100,0,0">
<Grid x:Name="Back">
<Grid.Effect>
<DropShadowEffect ShadowDepth="1" Direction="-90" BlurRadius="10" Opacity="0.25"/>
</Grid.Effect>
<Border Background="Black" CornerRadius="3" Opacity="0.15"/>
<Border CornerRadius="2" Margin="1" Background="#229C47"/>
</Grid>
<Grid x:Name="Content_Area" Margin="12">
<Image x:Name="Image" Stretch="None" Height="99" Grid.Row="1"/>
<TextBlock x:Name="Info" TextWrapping="Wrap" Text="Starting..." Grid.Row="2" Margin="12,12,12,0" Foreground="White"/>
</Grid>
</Grid>
<WindowsFormsHost Height="325" Name="splashControl" Margin="54,12,64,24" Width="460" HorizontalAlignment="Center" Background="Transparent">
<wf:SplashControl />
</WindowsFormsHost>
</Grid>
</Window>
I'm fighting with the same problem now.
The problem here is the AllowsTransparency="True" of the window, that is incompatible with showing WinForms controls with WindowsFormsHost.
Take a look here.
Doe's anybody knows what wrong with markup below
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DoubleAnim.MainPage"
Width="640" Height="480">
<UserControl.Resources>
<Storyboard x:Name="Storyboard1">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="objectToAnimate">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="400"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Rectangle x:Name="objectToAnimate" Fill="#FF0000F9" HorizontalAlignment="Left" Canvas.Top="164" Height="100" Stroke="Black" VerticalAlignment="Top" Width="192" RadiusY="8" RadiusX="8" >
</Rectangle>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="132,180,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</UserControl>
Everything looks good but it not works:((
Doe's silverlight know how to animate canvas.top and canvas.left?
Or maybe that I have missed something?
You need a canvas object. Replace Grid with Canvas.
The Canvas.Top and Canvas.Left attached properties are only used if the Rectangle (or UIElement) is hosted in a Canvas panel. The Grid uses different logic and attached properties to layout it's children.