Below i have two control templates that are used as cell templates for two different columns in a grid. You'll notice that both columns are bound to the same model properties (Code and Value), but use a converter to display those values differently. Both control template also use the same Style to 'blink' the cell when data is changed.
this works, but not exactly the way i want it. Right now, when either Data.Code or Data.Value changes, BOTH column cells Blink. What i want is if Data.Code == "CodeA", then the column using template CDisplay2 should not blink (infact, it should not display anything). And if Data.Code == "CodeB", then the cell using template CDisplay1 should not blink.
To achieve this it would be great if i could conditionally apply the style template based on Data.Code, but i can't figure out how to do that. Anythoughts on this? How can i selectively apply a style to a multiple controls bound to same model property based on a particular property value?
<Style x:Key="FlashStyle" TargetType="TextBlock" >
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames RepeatBehavior="4x" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.4" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="CDisplay1" >
<Grid>
<TextBlock Style="{StaticResource Flash1}" >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource conv}" ConverterParameter="CodeA" NotifyOnTargetUpdated="True">
<Binding Path="Data.Code" />
<Binding Path="Data.Value" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="CDisplay2" >
<Grid>
<TextBlock Style="{StaticResource Flash1}" >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource conv}" ConverterParameter="CodeB" NotifyOnTargetUpdated="True">
<Binding Path="Data.Code" />
<Binding Path="Data.Value" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</ControlTemplate>
I used a trigger to set the binding conditionally. this effect of this is that FlashStyle won't execute for both controls since some cells won't be bound and the converter won't be invoked.
Related
So I have this style that targets my ToggleButton, and I am trying to change the HorizontalAlignment of the border named ThumbCircle from Left to Right, I did read that changing that property is not as easy as just changing the value, I'm going to have to do some sort of LayoutTransform, however that didn't seem to work, when I click my button nothing happens, it doesnt move.
So my question is, how do I get the ThumbCircle to move to the right side of the Border that it's currently placed a.
<Style x:Key="MyToggleButton"
TargetType="{x:Type ToggleButton}">
<Style.Resources>
<Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
<Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border Width="50" Height="12" Background="White" CornerRadius="6">
</Border>
<Border Width="25"
Background="#2ecc71"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
HorizontalAlignment="Left"
x:Name="ThumbCircle">
<Border.Triggers>
<EventTrigger RoutedEvent="PreviewMouseDown">
<BeginStoryboard>
<Storyboard>
<!-- Dont forget easing -->
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" Storyboard.TargetName="ThumbCircle" To="10" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why nothing is showing up
The reason why you don't see anything moving is because you're targeting the (LayoutTransform).(ScaleTransform.ScaleX) property of your ThumbCircle, but it doesn't have any value set to its LayoutTransform property.
If you add this to your ThumbCircle Border:
<Border.LayoutTransform>
<ScaleTransform/>
</Border.LayoutTransform>
Then you will see something happening. But you'll see a scaling, not a translation! What you want is to translate from one side to the other.
The intuitive fix doesn't work...
The easiest way would have been to first replace the LayoutTransform with a RenderTransform and the ScaleTransform with a TranslateTransform like this:
<Border.RenderTransform>
<TranslateTransform x:Name="MyTranslate"/>
</Border.LayoutTransform>
Then give a name to your Grid like this:
<Grid x:Name="MyGrid">
...
</Grid>
And then animating the X property of the TranslateTransform from 0 to your Grid.ActualWidth like this:
<!-- This won't run -->
<DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="MyTranslate" To="{Binding ElementName=MyGrid, Path=ActualWidth}" Duration="0:0:0.5" />
But it is not possible to achieve this as it is not possible to set a Binding on any property of an Animation when used like this, because WPF makes some optimizations that prevent this as explained here.
A XAML-intensive way to do it
So a way to do it is to define proxy elements whose property we animate from 0 to 1 ,and we make the multiplication with MyGrid.ActualWidth at another location.
So your whole XAML style becomes:
<Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}">
<Style.Resources>
<Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
<Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
<!-- Aded some converters here -->
<views:MultiMultiplierConverter x:Key="MultiMultiplierConverter"></views:MultiMultiplierConverter>
<views:OppositeConverter x:Key="OppositeConverter"></views:OppositeConverter>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid x:Name="ContainerGrid">
<Border Width="50" Height="12" Background="Red" CornerRadius="6">
</Border>
<Border Width="25"
Background="#2ecc71"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
HorizontalAlignment="Left"
x:Name="ThumbCircle">
<Border.Resources>
<!-- Proxy object whose X property gets animated from 0 to 1. -->
<!-- Could be any DependencyObject with a property of type double. -->
<TranslateTransform x:Key="unusedKey" x:Name="Proxy"></TranslateTransform>
</Border.Resources>
<Border.RenderTransform>
<TransformGroup>
<!-- Main translation to move from one side of the grid to the other -->
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
<Binding ElementName="Proxy" Path="X"></Binding>
<Binding ElementName="ContainerGrid" Path="ActualWidth"></Binding>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
<!-- Secondary translation to adjust to the actual width of the object to translate -->
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
<Binding ElementName="Proxy" Path="X"></Binding>
<Binding ElementName="ThumbCircle" Path="ActualWidth" Converter="{StaticResource OppositeConverter}"></Binding>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
</TransformGroup>
</Border.RenderTransform>
<Border.Triggers>
<EventTrigger RoutedEvent="MouseDown">
<BeginStoryboard>
<Storyboard>
<!-- Dont forget easing -->
<DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And you would need to define two IValueConverter to perform some basic arithmetic operations on your Bindings:
One for multiplying all supplied values in a MultiBinding:
/// <summary>
/// Defines a converter which multiplies all provided values.
/// The given parameter indicates number of arguments to multiply.
/// </summary>
public class MultiMultiplierConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
double result = 1;
int count = int.Parse((string)parameter);
for (int i = 0; i < count; i++) {
result *= (double)values[i];
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
throw new NotSupportedException("Cannot convert back");
}
}
And one to multiply the input by -1:
/// <summary>
/// Defines a converter which multiplies the provided value by -1
/// </summary>
public class OppositeConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return (dynamic)value * -1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotSupportedException("Cannot convert back");
}
}
This is not an elegant way but it works!
How to implement back and forth animations?
So far we managed to animate the thumb to the right, on click. But that's not the whole point, is it?
What we are templating is a ToggleButton: at every click, an animation to the opposite side should be triggered. More exactly, whenever the IsChecked property gets True, we should trigger an animation to the right, and whenever the IsChecked property gets False, we should trigger an animation to the left.
This is possible by adding some Trigger objects in the ControlTemplate.Triggers collection. The Trigger shall be hooked to the IsChecked property (which we have no control over) and listen to its changes. We can specify an EnterAction which is our animation to the right, and an ExitAction which is our animation to the left.
The full Style becomes:
<Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}">
<Style.Resources>
<Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
<Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
<!-- Aded some converters here -->
<views:MultiMultiplierConverter x:Key="MultiMultiplierConverter"></views:MultiMultiplierConverter>
<views:OppositeConverter x:Key="OppositeConverter"></views:OppositeConverter>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<!-- Animation to the right -->
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!-- Dont forget easing -->
<DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<!-- Animation to the left -->
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<!-- Dont forget easing -->
<DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
<Grid x:Name="ContainerGrid">
<Border Width="50" Height="12" Background="Red" CornerRadius="6">
</Border>
<Border Width="25"
Background="#2ecc71"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
HorizontalAlignment="Left"
x:Name="ThumbCircle">
<Border.Resources>
<!-- Proxy object whose X property gets animated from 0 to 1. -->
<!-- Could be any DependencyObject with a property of type double. -->
<TranslateTransform x:Key="unusedKey" x:Name="Proxy"></TranslateTransform>
</Border.Resources>
<Border.RenderTransform>
<TransformGroup>
<!-- Main translation to move from one side of the grid to the other -->
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
<Binding ElementName="Proxy" Path="X"></Binding>
<Binding ElementName="ContainerGrid" Path="ActualWidth"></Binding>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
<!-- Secondary translation to adjust to the actual width of the object to translate -->
<TranslateTransform>
<TranslateTransform.X>
<MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
<Binding ElementName="Proxy" Path="X"></Binding>
<Binding ElementName="ThumbCircle" Path="ActualWidth" Converter="{StaticResource OppositeConverter}"></Binding>
</MultiBinding>
</TranslateTransform.X>
</TranslateTransform>
</TransformGroup>
</Border.RenderTransform>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that this Trigger is only accepted inside a ControlTemplate.Triggers collection, it is not possible to put such a Trigger in the original Border.Triggers collection, you can read more about it here.
Given the following :
<Viewbox>
<Foo:Bar
x:FieldModifier="private"
x:Name="fooBar"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Foo:Bar.RenderTransform>
<TransformGroup>
<ScaleTransform
x:FieldModifier="private"
x:Name="xfScale"/>
<RotateTransform
x:FieldModifier="private"
x:Name="xfRotate"/>
</TransformGroup>
</Foo:Bar.RenderTransform>
<Foo:Bar.Style>
<Style TargetType="{x:Type Foo:Bar}">
<Style.Triggers>
<DataTrigger
Binding="{
Binding Flip,
RelativeSource={
RelativeSource AncestorType={
x:Type local:myFooBar}}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty=""/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Foo:Bar.Style>
</Foo:Bar>
</Viewbox>
Which is for a new component that is basically a fancy label stuck inside of a ViewBox (for auto-scaling the label), what do I need to point the Storyboard.TargetProperty at to be able to animate, say, the RotateTransform Angle property?
Your TargetName will need to be set for your xfScale / xfRotate named transforms respectfully.
Your TargetProperty will be the properties of the transforms used.
Like for Scale;
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"
and
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
Except that only specifies the Property, you still need to provide a Value to animate to. So in it's entirety, it would become something like;
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="xfScale"
Storyboard.TargetProperty="X">
<SplineDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
Or for Rotate you need your Angle Property. It's worth mentioning, Blend makes this stuff much quicker/easier to do than by hand, especially for complex animations.
Hope this helps, cheers.
I have this Style:
<Style x:Key="BlinkStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BlinkForError, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type loc:DevicesRepositoryEditorUserControl}}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="BlinkBeginStoryboard">
<Storyboard>
<ColorAnimation To="Red" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
FillBehavior="Stop" Duration="0:0:0.4" RepeatBehavior="Forever" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="BlinkBeginStoryboard" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Whenever the bound dependency-property BlinkForError is set to True, it start blinking. It works great, like this:
<!-- When BlinkForError set to True, this TextBox, named "One", blinks: -->
<TextBox Name="One" Style="{StaticResource ResourceKey=BlinkStyle}"/>
Thing is that I want exactly the same thing, but bound to another dependency-property, say AnotherBlinkForError:
<!-- When AnotherBlinkForError set to True, this TextBox, named "Two", blinks: -->
<TextBox Name="Two" Style="{StaticResource ResourceKey=AnotherBlinkStyle}"/>
I can duplicate the whole style and only change the DataTrigger's Binding part.
Is there a way to avoid this duplication, reuse the same Style twice with two different bindings?
You could try and make use of the Tag properties on your TextBoxes and bind them to the BlinkForError and BlinkForAnotherError. In your style definition the binding will check the Tag value (you'd probably have to use RelativeSource and FindAncestor options) instead of the Blink properties.
But to be honest, if there are only two TextBoxes and respective error properties I would go with two separate styles as it's just less hassle.
I saw some examples for changing colors and brushes for selected item in a ListBox
I was wondering if there is a way to change visual properties of an item in a list box based on events in our code
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="spSceneThumb" Width="110">
<Border BorderThickness="1" Background="#FFfcfcfc" BorderBrush="#aaaaff" >
<StackPanel></StackPanel>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Let say I want to change border color of 5th item based on some event
I tried IValueConverter But changes to property wont effect border color
You can declare a custom RoutedEvent that you could listen to with an EventTrigger. You can find out how to declare a custom RoutedEvent in the How to: Create a Custom Routed Event page on MSDN. Once created, you can reference your custom event using the class name before the event name and not forgetting the XAML Namespace Prefix that you define for the namespace where it was declared. Something like this:
RoutedEvent="YourNamespacePrefix:YourClassName.YourEventName"
However, changing discrete values like Brushes is not so simple using an EventTrigger. You'll have to use a Storyboard with a DiscreteObjectKeyFrame element. You could try something like this:
<DataTemplate x:Key="Something">
<StackPanel Name="spSceneThumb" Width="110">
<Border Name="Border" BorderThickness="1" Background="#FFFCFCFC">
<StackPanel>
...
</StackPanel>
<Border.Resources>
<SolidColorBrush x:Key="EventBrush" Color="Red" />
</Border.Resources>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="#FFAAAAFF" />
<Style.Triggers>
<EventTrigger RoutedEvent="Prefix:YourClassName.YourEventName">
<EventTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="Border"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{StaticResource EventBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.EnterActions>
</EventTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</StackPanel>
</DataTemplate>
below i have a control template that comprises of two text box. The control template is used as a grid's cell display template and the datacontext is a bindinglist of my model object (which implement INotifyPropertyChanged). The second text box is initially Collapsed. However when "Ask" and "Bid" price updates happen, i want to 'flash" this text box by toggling visibility for about 1 second.
The problem i'm seeing is that on initial load of the form, i do see the 2nd text box flash, but after that...nothing. Interestingly if i click on a cell grid (which activates the edit template) then click out of the cell (which reverts back to the display template, which is the template shown below), the 2nd textbox then flashes.
Can anyone see an issue why the 2nd textbox would not 'flash' when AskPrice or BidPrice changes? I do see that the converter is being invoked as expected, but the update is not triggering the storyboard animation.
<Style x:Key="PriceSpreadAlertStyle" TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated" >
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00.2" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:00.4" Value="{x:Static Visibility.Collapsed}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:00.6" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:00.8" Value="{x:Static Visibility.Collapsed}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01.0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01.2" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="QuoteDisplayTemplate" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding QuotePrice}" />
<TextBlock Style="{StaticResource PriceSpreadAlertStyle}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource AskBidToSpreadConverter}">
<Binding Path="AskPrice" UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True" />
<Binding Path="BidPrice" UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</ControlTemplate>
Check to see that its 'getting' your value in the code behind when you do a NotifyPropertyChanged.
For the same reason, check for any binding expression errors when you initially start up.
Also, you could try changing the binding modes to oneway, I didn't think so but they could be getting set to a onetime binding preventing it from handling the changed event.
EDIT:
Have you tried using the SourceUpdated RoutedEvent instead of the TargetUpdated?
The solution that i found was to move 'NotifyOnTargetUpdated="True"' from the Binding element to the MultiBinding element.