Cannot animate (0).(1) on an immutable object? - wpf

I have a ComboBox with a custom theme I wrote, and I get the error message "Cannot animate (0).(1) on an immutable object." This specifically happens when I set its selectedindex after the user selects one of the options in the combobox.
Doing some research online, I found that this is a common issue with databound items or dynamic resources. I'm not using any databound resources, but what I think is happening is since the combobox is collapsed, it tries to set the state of a button that doesn't exist. I narrowed it down to this code:
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource spPressedStateBrush}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="{DynamicResource spOverStateBrush}" />
</Trigger>
</ControlTemplate.Triggers>
Which depends on these Dynamic Resources:
<LinearGradientBrush x:Key="spOverStateBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#2C8CBF" Offset="0" />
<GradientStop Color="#2793BF" Offset="0.5" />
<GradientStop Color="#2483BF" Offset="0.5001" />
<GradientStop Color="#2C8CBF" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="spPressedStateBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#0C6C9F" Offset="0" />
<GradientStop Color="#07739F" Offset="0.5" />
<GradientStop Color="#04639F" Offset="0.5001" />
<GradientStop Color="#0C6C9F" Offset="1" />
</LinearGradientBrush>
So I'm pretty sure those dynamic resources are the culprit, but how would I solve this problem?

After painstakingly trying to debug it by comparing the code to the original Control Template, I figured out that transporting all of the resources that my control uses into the file itself, and replacing all DynamicResources with StaticResources and that fixed the bug I was having.

Be sure that both this two (spOverStateBrush or spPressedStateBrush) Brushes are not used in different place in your code.
In order to be animatable, the Background Brush (spOverStateBrush or spPressedStateBrush) of the Border must be mutable, which the default value isn't.
If you use one of these two Brushes in another place, You should assign a new LinearGradientBrush before animating something like this code:
LinearGradientBrush gradient = new LinearGradientBrush();
gradient.StartPoint = new Point( 0.5, 0 );
gradient.EndPoint = new Point( 0.5, 1 );
gradient.GradientStops.Add(new GradientStop(Colors.Black, 0));
gradient.GradientStops.Add(new GradientStop(Color.FromArgb(100,69,87,186), 1));
Border.Background = gradient;

Related

WPF DataGrid row color by item property name

I have this DataGridRow template (it's simplified):
<Style TargetType="{x:Type DataGridRow}" x:Key="DataGridRowStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRow}">
<Grid>
<Border x:Name="OverlayBorder" Opacity="0.08">
<Border.Background>
<LinearGradientBrush EndPoint="0,0" StartPoint="10,10"
MappingMode="Absolute" SpreadMethod="Repeat">
<GradientStop Color="{Binding EntityState, Converter={resources:EntityStateToColorConverter}}" Offset="0" />
<GradientStop Color="{Binding EntityState, Converter={resources:EntityStateToColorConverter}}" Offset="0.4" />
<GradientStop Color="Transparent" Offset="0.4" />
<GradientStop Color="Transparent" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Border x:Name="DGR_Border"
Background="{TemplateBinding Background}">
<DataGridCellsPresenter />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource DataGridRowBackgroundHover}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource DataGridRowBackgroundSelected}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I have some class:
public class EntityWithState
{
public EntityStateEnum EntityState { get; set; }
}
The template works fine with class EntityWithState. Look at this row
<GradientStop Color="{Binding EntityState, Converter={resources:EntityStateToColorConverter}}" Offset="0" />
depending on EntityWithState.EntityState DataGridRow sets gradient background.
But if I have another class:
public class EntityWithEntityWithState
{
public EntityWithState EntityWithState { get; set; }
}
Our template must be:
<GradientStop Color="{Binding EntityWithState.EntityState, Converter={resources:EntityStateToColorConverter}}" Offset="0" />``
My question is how can I use same template for several classes?
I think Attached Propery helps me. For example:
<DataGrid UiExtensions:DataGrid.StatePropName="EntityWithState.EntityState">
...
</DataGrid>
But I do not know how to implement it. Or if there is another solution...
UPDATE (22.11.2017)
I could use
public class EntityWithEntityWithStateViewModel
{
public EntityWithEntityWithState EntityWithEntityWithState { get; set; }
public EntityStateEnum EntityState => EntityWithEntityWithState.EntityWithState.EntityState;
}
but I want do it without ViewModel access, only with XAML and UI
UPDATE 2 (23.11.2017)
In otherwords I need something like DisplayMemberPath of ComboBox or ListBox
My question is how can I use same template for several classes?
Short answer: You can't.
You can't replace only the binding path and re-use the rest of the template I am afraid. A template is always defined "as a whole":
WPF: Is there a way to override part of a ControlTemplate without redefining the whole style?
So there is no way to do this in XAML. You might want to consider creating the templates programmatically:
How generate custom columns for FrameworkElementFactory(typeof(Datagrid))?

VsBrush doesn't work in WPF ResourceDictionary

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.)

Change background color with color gradients

I want to change the background color from my combobox.
But I would like to retain the color gradients.
I've tried using this code but still does not get the effect.
<Setter Property="Background" Value="White"/> <!-- It's only white :( -->
<ComboBox>
<ComboBox.Background>
<LinearGradientBrush EndPoint="0,1">
<GradientStopCollection>
<GradientStop Color="Blue" Offset="0.5" />
<GradientStop Color="White" Offset="0.5" />
</GradientStopCollection>
</LinearGradientBrush>
</ComboBox.Background>
</ComboBox>
This will change the background color. Change the Color and Offset to get your desired result.

Styling in WPF is not being applied automatically

I have a simple style that I'm trying to apply to all of the buttons in my app:
<LinearGradientBrush x:Key="ButtonBackgroundBrush">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Red" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush Color="Purple" x:Key="ButtonForegroundBrush" />
<SolidColorBrush Color="LimeGreen" x:Key="ButtonBorderBrush" />
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource ButtonBackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource ButtonForegroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorderBrush}" />
</Style>
I have this in my app.xaml file, so it is available to everything in my app. If I put a button on a page, the styles does not apply. If I add an "x:key" to the style and then add that style name to the button, the style is applied. Therefore, I know the style is written correctly and is within scope of the button. Does anyone have any ideas as to why the style does not automatically apply to the buttons as it should if I leave off the x:key from the style?
Must have been a VS2010 glitch. I added a new page, put styles in the app.xaml file and they applied to the controls on the new page. Opened up the old page and, as if by Microsoft magic, the styles started applying. Truly strange.

WPF Change Style's Brush Color

I have the following styles in WPF to draw and color a box, which is a custom control with various PART_Name items defined in a ResourceDictionary:
<ResourceDictionary>
.
.
.
<Brush x:Key="BoxStroke">#FFD69436</Brush>
<LinearGradientBrush x:Key="BoxBrush" StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FAFBE9" Offset="0" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<Style x:Key="BoxStyle" TargetType="Path">
<Setter Property="Fill" Value="{DynamicResource BoxBrush}"/>
<Setter Property="Stroke" Value="{DynamicResource BoxStroke}"/>
</Style>
<Style x:Key="Box" TargetType="Path" BasedOn="{StaticResource BoxStyle}">
<Setter Property="Data" Value="M 0,0 H 60 V40 H 0 Z"/>
</Style>
.
.
.
</ResourceDictionary>
My question is how can I access the GradientStop color property for the brush?
For instance if the user clicks on the box turn it from "Green" to "Blue".
I've got all of the appropriate code to handle user interaction, I am just stumped on how to change the color of the brush.
The easiest way to do this would be to use databinding instead. Bind the view to an object which has a property that contains the value of the colour you want to change. Then bind that property value to the gradient. When the button is clicked, modify that property and the databinding mechanism will update the colour on screen for you. Just make sure you either implement INotifyPropertyChanged or make the property a dependency property.
Good luck!
Once you can access the brush in code, you will just need to assign it a Color value. For example the System.Windows.Media.ColorConverter class will translate hex/web colors into System.Windows.Media.Color values.
Here's a sample, hopefully this is the general idea of what you're asking about:
System.Windows.Media.LinearGradientBrush gb = new System.Windows.Media.LinearGradientBrush();
gb.GradientStops[0].Color = (Color)ColorConverter.ConvertFromString("#FF00FF00");

Resources