If I change some resource used as StaticResource then all the controls which referring to is affected.
And it does not in case the resource is referred as DynamicResource.
But how about some resource referred as StaticResource in a DynamicResource?
<Color x:Key="Color">#000000</Color>
<LinearGradientBrush x:Key="GradientBrush" EndPoint="0,0" StartPoint="0,1" >
<GradientStop Color="{StaticResource Color}" Offset="0" />
<GradientStop Color="{StaticResource Color}" Offset="1" />
</LinearGradientBrush>
<DrawingBrush x:Key="TabItemBrush" >
<DrawingBrush.Drawing>
<GeometryDrawing Brush="{StaticResource GradientBrush}">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0 100 100 100"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="Root">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected" >
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Panel.Background).(DrawingBrush.Drawing).(GeometryDrawing.Brush).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Red" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Panel.Background).(DrawingBrush.Drawing).(GeometryDrawing.Brush).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Blue" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border" Background="{DynamicResource TabItemBrush}">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center"
HorizontalAlignment="Center" ContentSource="Header"
Margin="12,2,12,2" RecognizesAccessKey="True" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Border in TabItem refers TabItemBrush using DynamicResource.
TabItemBrush refers GradientBrush using StaticResource.
GradientBrush refers Color using StaticResource.
<StackPanel>
<TabControl>
<TabItem Header="AAA" >AAA</TabItem>
<TabItem Header="BBB" >BBB</TabItem>
<TabItem Header="CCC" >BBB</TabItem>
</TabControl>
<Border Background="{DynamicResource GradientBrush}" Height="1000" Width="1000"/>
</StackPanel>
I thought that all of TabItem change to same color when I change a selected item in TabControl because all of resources is referred as StaticResource except TabItemBrush.
But only the color of selected item is blue and the others red.
Moreover if i change all the StaticResource to DynamicResource, it works incorrectly ( all red or all blue).
Why StaticResource works as if it is not shared and DynamicResource as shared?
I found your question very difficult to understand. However, I think you are confused because you are not understanding the difference between StaticResource and DynamicResource.
As the names suggest, StaticResource references will/can never change (they are static).
Resources referenced using StaticResource are resolved only once at compile-time.
As the names suggest, DynamicResource references can change (they are dynamic).
Resources referenced using DynamicResource are resolved at runtime.
Since StaticResource references are resolved at compile-time, the XAML parser can avoid the overhead of creating an intermediate lookup expression (which is then executed at runtime).
This is why you should always avoid DynamicResource with the goal to improve the application's performance.
StaticResource does not allow forward references, whereas DynamicResource does - but the forward reference aspect doesn't matter in your context.
"If I change some resource used as StaticResource then all the
controls which referring to is affected."
This is definitely not correct. You can't change resources referenced as StaticResource and then observe that those changes update the referencing objects - this can't have never happened (see explanation of the fundamental characteristics above).
You probably meant something different here.
"I thought that all of TabItem change to same color when I change a selected >item in TabControl because all of resources is referred as StaticResource >except TabItemBrush.
But only the color of selected item is blue and the others red."
This is the correct behavior. You have defined the VisualState objects for "Selected" and "Unselected" with the VisualStateManager. According to these states clicking a TabItem will modify the Background of the selected item to blue and all other unselected items to red.
"Moreover if i change all the StaticResource to DynamicResource, it works >incorrectly ( all red or all blue)."
Here comes in the special behavior of the XAML engine in context of animations: it will freeze the Storyboard.
This means, all participating child instances like AnimationTimeline (e.g., ColorAnimation), or Animatable types in general, are frozen and therefore not changeable (they all inherit Freezable).
As a consequence, all referenced resources like Brush must be static and known at compile-time: you can't reference resources using DynamicResource if the referencing instance needs to be frozen.
Resources used in a Storyboard must be referenced as StaticResource.
Now, given the case that an element, for example a Border, uses DynamicResource to reference a resource that itself contains references to other resources:
If the element uses DynamicResource to reference a pure static resource (a resource that doesn't itself references other resources using DynamicReference), then the XAML engine will optimize the reference by treating it as a StaticResource, to avoid the overhead, and store it in the internal lookup table for the static resources (remember, StaticResource is much cheaper in terms of performance costs than looking up a DynamicResource). This resource can be targeted by a Storyboard animation as it is now static:
<ResourceDictionary>
<Color x:Key="Color">#000000</Color>
<!-- Resource is treated as a static resource -->
<LinearGradientBrush x:Key="GradientBrush" EndPoint="0,0" StartPoint="0,1" >
<GradientStop Color="{StaticResource Color}" Offset="0" />
<GradientStop Color="{StaticResource Color}" Offset="1" />
</LinearGradientBrush>
</ResourceDictionary>
<!-- The lookup behavior is optimized to StaticResource.
A Storyboard therefore will be able to animate the Background resource.
-->
<Border Background="{DynamicResource GradientBrush}" />
If the resource that an element references is itself referencing at least one resource using DynamicResource, then the reference will remain dynamic and will prevent e.g., the Storyboard from being frozen and the animation won't work:
<ResourceDictionary>
<Color x:Key="Color">#000000</Color>
<!-- Resource is treated as a dynamic resource,
because it contains at least one DynamicResource reference.
-->
<LinearGradientBrush x:Key="GradientBrush" EndPoint="0,0" StartPoint="0,1" >
<GradientStop Color="{DynamicResource Color}" Offset="0" />
<GradientStop Color="{StaticResource Color}" Offset="1" />
</LinearGradientBrush>
</ResourceDictionary>
<!-- The lookup behavior is now DynamicResource and not optimized.
A Storyboard won't be able to animate the properties of the resource as it can't be frozen.
-->
<Border Background="{DynamicResource GradientBrush}" />
Another effect you ecounter is that all items have the same Background when you select a TabItem.
This is related to the way the XAML engine handles resources. Referenceing a resource using StaticResource, results in the resource being shared by default.
You have two solutions to fix this issue:
Declare each participating resource of the static reference as non-shared by setting the x:Shared attribute to false:
<ResourceDictionary>
<Color x:Key="Color"
x:Shared="False">#000000</Color>
<LinearGradientBrush x:Key="GradientBrush"
x:Shared="False"
EndPoint="0,0"
StartPoint="0,1" >
<GradientStop Color="{StaticResource Color}" Offset="0" />
<GradientStop Color="{StaticResource Color}" Offset="1" />
</LinearGradientBrush>
</ResourceDictionary>
<Border Background="{StaticResource GradientBrush}" />
Alternatively, define the animated resource as inline. This way each TabItem border will have its own/non-shared Brush resource:
<ResourceDictionary>
<Color x:Key="Color">#000000</Color>
</ResourceDictionary>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="0,0" StartPoint="0,1" >
<GradientStop Color="{StaticResource Color}" Offset="0" />
<GradientStop Color="{StaticResource Color}" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
Related
tl;dr
Problem description
First of all, I'm new to WPF and I have little to now idea what I'm doing...
I'm developing a Visual Studio 2013 Extension in which I create a custom Tool Window. Since the WPF controls don't inherit themes from parent (i.e. Visual Studio main window), you have to to it manually.
I read that I should use the Microsoft.VisualStudio.Shell.VsBrushes or the Microsoft.VisualStudio.PlatformUI.EnvironmentColors classes as they define the Visual Studio shell theme specific constants. (See example here.) This works all fine as long as I use this within a Style tag.
However, Microsoft's Menu and MenuItem ControlTemplate Example explains how to do a proper MenyItem template using <ContentTemplate>.
The problem is, that neither the VsBrush nor the EnvironmentColors don't work within the <ContentTemplate>. Either there is a generic exception complaining when I set the color for <GradintStop> (no details, what the problem is), or the UI just hangs, doesn't even load. In this later case when I break the application, I always en up in MS.Win32.UnsafeNativeMethods.GetMessageW() function.
Questions
Could someone please explain what I'm doing wrong and why I can't use the VsBrushes and EnvironmentColors classes in my <ContentTemplate>?
How can I properly style my Visual Studio Extension using the suggested <ContentTemplate> format?
Attempts To Solve The Issue
I checked in the constructor of the whole package, that the color constants of the VsBrushes class can be fetched. Only after the constructor does the UI hang, so the VsBrushes values must be initialized by the time the XAML is processed.
As pointed out above, without the use of <ControlTemplate> the constants can be used.
Investigated the exception: it was thrown when the parser tried to interpret the Color property of the <GradientStop> tag. No explanation what exactly failed. (After a while -- as the code changed a bit -- the exception ceased and the UI hang started.)
If I change the LinearGradientBrush to SolidColorBrush the problem still persists (of course the exception is slightly different this time): 'Set property 'System.Windows.Media.SolidColorBrush.Color' threw an exception.
The problem is not <MenuItem> specific. It can be reproduced with <Button> and I guess with any arbitrary WPF control.
Source
Here's the code I use for defining my style for MenuItems:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vsShell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.12.0"
xmlns:vsUI="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.12.0">
<Color x:Key="MyColor">#FFFFFF00</Color>
<Style x:Key="{x:Type Menu}" TargetType="{x:Type Menu}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Menu}">
<Border BorderThickness="1">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="{DynamicResource MyColor}" Offset="0.0" />
<GradientStop Color="{DynamicResource {x:Static SystemColors.ActiveBorderColorKey}}" Offset="0.1" />
<GradientStop Color="#FF00FF00" Offset="0.5" />
<!-- The following 3 do not work -->
<GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderBrushKey}}" Offset="0.8" />
<GradientStop Color="{DynamicResource {x:Static vsShell:VsBrushes.AccentBorderKey}}" Offset="0.8" />
<GradientStop Color="{DynamicResource VsBrush.AccentBorder}" Offset="1.0" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.BorderBrush>
<Border.Background>
<LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Blue" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<StackPanel ClipToBounds="True" Orientation="Horizontal" IsItemsHost="True" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Call Stack
This is where the process "hangs":
[Managed to Native Transition]
> WindowsBase.dll!MS.Win32.UnsafeNativeMethods.GetMessageW(ref System.Windows.Interop.MSG msg, System.Runtime.InteropServices.HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax) Line 566 C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage) Line 391 C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) Line 979 C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) Line 961 C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() Line 1059 C#
Microsoft.VisualStudio.Shell.12.0.dll!Microsoft.Internal.VisualStudio.PlatformUI.BackgroundDispatcher.ThreadProc(object arg) Unknown
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) Unknown
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart(object obj) Unknown
Resolution
All these following 3 lines cause compilation error, because the compiler expects a Color but I gave it a Brush instead.
<GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderBrushKey}}" Offset="0.8" />
<GradientStop Color="{DynamicResource {x:Static vsShell:VsBrushes.AccentBorderKey}}" Offset="0.8" />
<GradientStop Color="{DynamicResource VsBrush.AccentBorder}" Offset="1.0" />
I should have use <GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderColorKey}}" Offset="0.8" /> instead. (Notice that I use AccentBorderColorKey instead of AccentBorderBrushKey.)
XAML is parsing string and tries to interpret that which leads to a simple fact: XAML is typeless (everything is a string). Since the error message ('Set property 'System.Windows.Media.LinearGradientBrush.Color' threw an exception.) is not really talkative, it doesn't really help you understand what you did wrong.
Lesson learned
Try to check the type manually if the compiler doesn't do it for you (or doesn't tell you that it is what causes the problem.)
Visual Studio 2010 | .NET/WPF 4.0
I think this might be a WPF bug, but I can't seem to find a bug report about it. To cover the possibility that I'm just missing something obvious, I turn to stackoverflow for answers!
Consider this xaml (nothing in the codebehind):
<Window x:Class="DownExpanders.BorderTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BorderTest" Height="300" Width="300">
<Window.Resources>
<Color x:Key="BackgroundColor" R="255" G="0" B="0" A="255"/>
<Color x:Key="BorderColor" R="0" G="0" B="255" A="255"/>
<SolidColorBrush x:Key="BorderColorBrush" Color="{DynamicResource BorderColor}"/>
</Window.Resources>
<Grid>
<Border BorderThickness="20">
<Border.Background>
<SolidColorBrush Color="{DynamicResource BackgroundColor}"/>
</Border.Background>
<Border.BorderBrush>
<SolidColorBrush Color="{DynamicResource BorderColor}"/>
</Border.BorderBrush>
</Border>
<Border Margin="40" BorderBrush="{DynamicResource BorderColorBrush}" BorderThickness="20"/>
</Grid>
</Window>
In the designer, it renders as expected. The outer border has a big blue border and a red background, the inner border has a big blue border. Great.
When I run the code, the outer border has NO border - it looks like it just doesn't load. The background is set to red correctly. Meanwhile, the inner border does load its blue border correctly.
If I change all "DynamicResource" to "StaticResource", it renders correctly when run. The inconsistency is really bugging me, and I can't figure it out.\
So:
Why doesn't DynamicResource work for BorderBrush?
Given #1, why does it work for Background?
Why does explicitly defining the solid color brush in the resources seem to fix things?
EDIT:
Looks like it's a bug that MS decided not to fix (thanks to Sheridan for the link): http://connect.microsoft.com/VisualStudio/feedback/details/589898/wpf-border-borderbrush-does-not-see-changes-in-dynamic-resource
This doesn't seem to be the case with the RadialGradientBrush.
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<Color x:Key="BackgroundColor" R="255" G="0" B="0" A="255"/>
<Color x:Key="BorderColor" R="0" G="0" B="255" A="255"/>
<SolidColorBrush x:Key="BorderColorBrush" Color="{DynamicResource BorderColor}"/>
</Grid.Resources>
<Border BorderThickness="20">
<Border.BorderBrush>
<RadialGradientBrush>
<GradientStop Color="{DynamicResource BorderColor}"/>
<GradientStop Color="{DynamicResource BorderColor}"/>
</RadialGradientBrush>
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="{DynamicResource BackgroundColor}"/>
</Border.Background>
</Border>
<Border Margin="40" BorderBrush="{DynamicResource BorderColorBrush}" BorderThickness="20"/>
</Grid>
</Window>
Apparently, the answer to your question is no, this behaviour is not a bug.
This issue was posted on the Microsoft Connect site by a user and the following reply was given:
DynamicResources are "looked up" at runtime rather than compile time.
The "Dynamic" refers not to "can be dynamically updated at any time"
but "we'll look it up later, when it's actually needed."
If you want to change the border brush at runtime, you'll need to
apply a Name="" attribute to the Border in order to touch it from the
codebehind, or you can use a Binding to set the value of the brush to
a DependencyProperty (if you're using the MVVM pattern or something
similar). Change the property and the border brush gets updated by the
binding system.
BTW, this would have been a good question over at StackOverflow--"Why
isn't my DynamicResource being updated?"
Personally, I like the last line best. Microsoft at its most useful! The page can be found here.
Another interesting thing is that it does not happen when use a Rectangle instead of a Border.
<Rectangle StrokeThickness="20">
<Rectangle.Stroke>
<SolidColorBrush Color="{DynamicResource BorderColor}"/>
</Rectangle.Stroke>
<Rectangle.Fill>
<SolidColorBrush Color="{DynamicResource BackgroundColor}"/>
</Rectangle.Fill>
</Rectangle>
It seems to be fixed in 4.5. In my case it works in Windows 8, but don't work in Windows XP (that don't have .net 4.5).
Here is a custom control which you can use in place of the Border. It fixes the problem with the BorderBrush property. It uses Rectangles which work as another answer indicates. Please note that this control will probably not match the performance of using the Border control but it does work, so I suggest only using it where necessary.
<Style TargetType="{x:Type controls:BorderFix}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:BorderFix}">
<DockPanel x:Name="PART_Container"
Background="{TemplateBinding Background}"
LastChildFill="True"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
<Rectangle x:Name="PART_LeftBorder"
DockPanel.Dock="Left"
Fill="{TemplateBinding BorderBrush}"
Width="{Binding Path=BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"/>
<Rectangle x:Name="PART_TopBorder"
DockPanel.Dock="Top"
Fill="{TemplateBinding BorderBrush}"
Height="{Binding Path=BorderThickness.Top, RelativeSource={RelativeSource TemplatedParent}}"/>
<Rectangle x:Name="PART_RightBorder"
DockPanel.Dock="Right"
Fill="{TemplateBinding BorderBrush}"
Width="{Binding Path=BorderThickness.Right, RelativeSource={RelativeSource TemplatedParent}}"/>
<Rectangle x:Name="PART_BottomBorder"
DockPanel.Dock="Bottom"
Fill="{TemplateBinding BorderBrush}"
Height="{Binding Path=BorderThickness.Bottom, RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter x:Name="PART_Content"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public sealed class BorderFix : ContentControl
{
static BorderFix()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BorderFix), new FrameworkPropertyMetadata(typeof(BorderFix)));
}
}
The fact that we have to do this is pretty ridiculous. Another answer suggests that this bug is fixed in the version of .NET used by Windows 8. I have not tested that but let us hope that is correct. .NET 4.5.51209 exhibits the same problem.
I'm new to WPF and still having some basic problems.
I have a control from devcomponents that defaults to a blue border. My textboxes etc. have a more grey colour. I want the devcomponents control to have the same border.
I look in the properties of a TextBox and see that BorderBrush is set to "System.Windows.Media.LinearGradientBrush" yet I can't put -
<WpfEditors:IntegerInput BorderBrush="System.Windows.Media.LinearGradientBrush"...
In fact, I can't put -
<TextBox BorderBrush="System.Windows.Media.LinearGradientBrush" ...
What magic am I missing?
Thanks.
To the property BorderBrush you have to assign a Brush (as you could guess by its name).
One kind of Brush is a LinearGradientBrush (the thing which makes a gradient between colors)
SolidColorBrush is another kind of Brush which could also get assigned.
As it looks as this kind of control you use has already assigned a LinearGradientBrush.
Now you can assign a Brush of your choice and override the already set Brush.
Example for a LinearGradientBrush:
<TextBox>
<TextBox.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Black" Offset="0.0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</TextBox.BorderBrush>
</TextBox>
If you want your border just in a a solid color you can also use a SolidColorBrush.
<TextBox.BorderBrush>
<SolidColorBrush Color="Red" />
</TextBox.BorderBrush>
or just use the existing Converter Color --> SolidColorBrush
<TextBox BorderBrush="Red" Text="bla bla" />
EDIT:
And if you want that all your controls have the same Border you can add a Brush to the ResourceDictionary of a container object and reuse it for all the controls...
<!-- Add the Brush as resource to the surrounding window -->
<Window.Resources>
<SolidColorBrush x:Key="controlBorderBrush" Color="Gray" />
</Window.Resources>
<!-- -->
<TextBlock BorderBrush="{StaticResource controlBorderBrush}" Text="huhuuu" />
<otherlib:SpecialTextBlockWithOverriddenProps BorderBrush="{StaticResource controlBorderBrush}" Text="hahaaaaaaa" />
I have written a control and successfully created a storyboard to cause an animation during triggered events. It changes the fill of an ellipse for a duration of time. Instead of writing a new RadialGradientBrush each time I need to change the fill, I provided two of them in the resources.
EDIT:
I have an Ellipse that is the main component to the control and is what is affected by the animation. It's implementation is simple and looks like this:
<Ellipse Name="myEllipse" Style="{StaticResource DimStyle}" />
When I add it to the storyboard (instead of referencing the brush as a resource), my animation works as intended. When I reference the brush as a resource I get this exception:
"Cannot find resource named 'IlluminatedStyle'. Resource names are case sensitive."
Inside of the storyboard, this is where it is currently referenced:
<UserControl.Resources>
<Storyboard x:Key="Foo">
<ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{StaticResource IlluminatedStyle}" />
<DiscreteObjectKeyFrame KeyTime="0:0:0.85" Value="{StaticResource DimStyle}" />
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
The styles are closely identical and only the GradientStop color properties differ so I'll provide only one style for an example.
The Style Referenced
<UserControl.Resources>
<Style x:Key="IlluminatedStyle" TargetType="Ellipse">
<Setter Property="Fill">
<Setter.Value>
<RadialGradientBrush>
<GradientStop Color="#FF215416" Offset="1"/>
<GradientStop Color="#FE38DA2E" Offset="0"/>
<GradientStop Color="#FE81FF79" Offset="0.688"/>
</RadialGradientBrush>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
So how do I correctly reference a style such as this in my Storyboard?
Note: The Storyboard and Style are both contained within the same UserControl.Resources tag but broken out for this example.
EDIT
I put the Style before the Storyboard in UserControl.Resources and now I get an exception stating:
"This Freezable cannot be frozen.
at System.Windows.Freezable.Freeze()
at System.Windows.Freezable.GetCurrentValueAsFrozen()
at System.Windows.Media.Animation.TimelineCollection.GetCurrentValueAsFrozenCore(Freezable source)
at System.Windows.Freezable.CloneCoreCommon(Freezable sourceFreezable, Boolean useCurrentValue, Boolean cloneFrozenValues)
at System.Windows.Media.Animation.Timeline.GetCurrentValueAsFrozenCore(Freezable sourceFreezable)
at System.Windows.Freezable.GetCurrentValueAsFrozen()
at System.Windows.Media.Animation.Clock..ctor(Timeline timeline)
at System.Windows.Media.Animation.TimelineGroup.AllocateClock()
at System.Windows.Media.Animation.Clock.AllocateClock(Timeline timeline, Boolean hasControllableRoot)"
There are three reasons why a Freezable cannot be frozen:
It has animated or data bound properties.
It has properties that are set by a dynamic resource.
It contains Freezable sub-objects that cannot be frozen.
So, first find out which Freezable is causing trouble and then check the above.
Seeing that I am new to WPF and XAML I made the mistake of making my resources a style and did not realize that I could have simply made the brushes a resource and avoid styles altogether.
I kept the reference to the DiscreteObjectKeyFrames' values as static to the new brush resources. I changed the Ellipse to this:
<Ellipse Name="myEllipse" Fill="{StaticResource DimBrush" />
The style property was removed and I assigned the brush to the fill property directly. In the ObjectAnimationUsingKeyFrames tag I added Fill as the Storyboard.TargetProperty since I was no longer using a style to dress up the fill. The DiscreteObjectKeyFrames now look like this:
<DiscreteObjectKeyFrame KeyTime="0:0:0.01" Value="{StaticResource IlluminatedBrush}" />
<DiscreteObjectKeyFrame KeyTime="0:0:0.85" Value="{StaticResource DimBrush}" />
My resources are much simpler without being wrapped in a style and IMO, more elegant. Also the brushes are defined before the animation in my final solution.
<UserControl.Resources>
<RadialGradientBrush x:Key="DimBrush" >
<GradientStop Color="#FF21471A" Offset="1"/>
<GradientStop Color="#FF33802F" Offset="0"/>
<GradientStop Color="#FF35932F" Offset="0.688"/>
</RadialGradientBrush>
<RadialGradientBrush x:Key="IlluminatedBrush">
<GradientStop Color="#FF215416" Offset="1"/>
<GradientStop Color="#FE38DA2E" Offset="0"/>
<GradientStop Color="#FE81FF79" Offset="0.688"/>
</RadialGradientBrush>
<!-- Storyboard code follows... -->
</UserControl.Resources>
Everything is now working as intended. The best assumption I can make is that styles are not freezable since they were the components that I removed and I no longer receive exceptions regarding a freezable that cannot be frozen.
In an XAML document, I have a gradient brush as a resource and a bunch of shapes that use this resource. I would like to animate the brush using a storyboard, but I don't know how to set the brush in resources as the target of storyboard. Simply using its name does not work, {StaticResource name} does not work either. Is it even possible?
I would prefer an XAML only solution, but if that does not work out, I'll use code-behind. If it lets me leave Storyboard.Target and Storyboard.TargetProperty unassigned.
EDIT: I would like to animate a gradient stop of the brush. The thing is that I can animate it easily when it's not a resource, but is applied directly on an object. I can do that by clicking in Expression Blend. I just don't know how to animate it when its a resource (i.e. what to put instead of the ?? in the code below (the storyboard was created for a rectangle))
code:
<UserControl.Resources>
<LinearGradientBrush x:Key="Outline" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#7F7CE3FF" Offset="0"/>
<GradientStop Color="#7F047695" Offset="1"/>
<GradientStop Color="#FFFFFFFF" Offset="0.942"/>
</LinearGradientBrush>
<Storyboard x:Key="Glitter">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="??" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.6000000" Value="0.529"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
...
It works when you animate the Background/Fill Property directly, using the name of the object (e.g. Rectangle) you want to animate as Storyboard.TargetName:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.Resources>
<LinearGradientBrush x:Key="Outline" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#7F7CE3FF" Offset="0"/>
<GradientStop Color="#7F047695" Offset="1"/>
<GradientStop Color="#FFFFFFFF" Offset="0.942"/>
</LinearGradientBrush>
</Grid.Resources>
<Border Name="border"
Background="{StaticResource Outline}"
Width="200" Height="200" />
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="border"
Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">
<SplineDoubleKeyFrame KeyTime="00:00:0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:1" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
Edit
From code behind it seems to be totally possible:
XAML:
<Grid Name="grid">
<Grid.Resources>
<LinearGradientBrush x:Key="Outline" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#7F7CE3FF" Offset="0"/>
<GradientStop Color="#7F047695" Offset="1"/>
<GradientStop Color="#FFFFFFFF" Offset="0.942"/>
</LinearGradientBrush>
</Grid.Resources>
<Border Background="{StaticResource Outline}"
Width="100" Height="100" HorizontalAlignment="Left" />
<Border Background="{StaticResource Outline}"
Width="100" Height="100" HorizontalAlignment="Right" />
</Grid>
C# code behind:
LinearGradientBrush b = grid.Resources["Outline"] as LinearGradientBrush;
b.GradientStops[0].BeginAnimation(GradientStop.OffsetProperty, new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(1))));
You cannot animate Properties of the type Brush, you can only animate types that have an appropiate Animation Class, for example DoubleAnimation, PointAnimation or ColorAnimation (note that the last one animates Properties of the type Color, not Brush).
However, some Brushes have DependencyProperties of the type double, that you could animate, for example the StartPoint and EndPoint Properties of the LinearGradientBrush-Class.
If you can elaborate on what the animation should do exactly, maybe we could find a workaround.
Edit:
To animate the Brush it would have to be declared in the scope of your Animation-Trigger, e.g. in the Data- or ControlTemplate. Animating a Resource via its key will not work.
Not sure exactly what you're trying to animate inside of the brush, but animating Brush resources can be very tricky. I don't have time to type everything out, but here's a little 'tutorial' on how to handle it:
Animating Brushes with ObjectAnimationUsingKeyFrames