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.
Related
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 have a Control that can be moved on by dragging. when i drag the control i have a code behind that changes a DependencyProperty that a TranslateTransform is bound to.
now i need to add a button that when is pressed it moves the control, and needs to update the DependencyProperty. I can move the control but can't figure out how to update the DependencyProperty.
code behind:
public partial class AirspeedIndicatorView : UserControl
{
public static readonly DependencyProperty WantedValueProperty =
DependencyProperty.Register("WantedValue", typeof(double), typeof(AirspeedIndicatorView),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WantedPropertyChanged));
public double WantedValue
{
get { return (double)GetValue(WantedValueProperty); }
set { SetValue(WantedValueProperty, value); }
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
WantedValue += e.VerticalChange;
}
}
XAML:
<Thumb Canvas.Top="-6" Height="12" Width="16" DragDelta="Thumb_DragDelta" x:Name="WantedThumb">
<Thumb.RenderTransform>
<TranslateTransform Y="{Binding WantedValue,ElementName=View}" />
</Thumb.RenderTransform>
</Thumb>
<Button Padding="1" Margin="1">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="OverridesDefaultStyle" Value="False" />
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="MoveWanted">
<Storyboard Target="{x:Reference WantedThumb}" TargetProperty="RenderTransform.Y" AutoReverse="False">
<DoubleAnimation BeginTime="00:00:00" Duration="0:0:0" By="-1" />
<DoubleAnimation BeginTime="00:00:00.5" Duration="0:0:1.5" By="-15" />
<DoubleAnimation BeginTime="00:00:02" Duration="0:0:1" By="-20" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="IsPressed" Value="False">
<Trigger.EnterActions>
<StopStoryboard BeginStoryboardName="MoveWanted" />
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
I don't have time to check it now, but you should be able to use the ObjectAnimationUsingKeyFrames class to update your DependencyProperty:
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Saved">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Double>10.0</System:Double>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
Actually, the DoubleAnimationUsingKeyFrames class might be better for you as it is intended to work with doubles, but the only down side is that I don't believe that you can data bind the numerical value, so it would have to be a hard coded value. From the last linked page:
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTranslateTransform"
Storyboard.TargetProperty="X"
Duration="0:0:6"
RepeatBehavior="Forever">
<!-- Using a LinearDoubleKeyFrame, the rectangle moves
steadily from its starting position to 500 over
the first 3 seconds. -->
<LinearDoubleKeyFrame Value="500" KeyTime="0:0:3" />
<!-- Using a DiscreteDoubleKeyFrame, the rectangle suddenly
appears at 400 after the fourth second of the animation. -->
<DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:4" />
<!-- Using a SplineDoubleKeyFrame, the rectangle moves
back to its starting point. The
animation starts out slowly at first and then speeds up.
This KeyFrame ends after the 6th
second. -->
<SplineDoubleKeyFrame KeySpline="0.6,0.0 0.9,0.00" Value="0" KeyTime="0:0:6" />
</DoubleAnimationUsingKeyFrames>
I have the following simple animation:
<Window x:Class="AnimationTest.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">
<StackPanel Background="Black">
<TextBox Name="Box" >
</TextBox>
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock" >
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation AutoReverse="True" To="#A933FF" Duration="0:0:1" Storyboard.TargetProperty="Foreground.Color" FillBehavior="Stop" IsCumulative="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
It just makes flash a value when the value changes. if you write a letter you see it flash correctly to the color set in the animation and go back. But if you click several times the duration is longer (which is the desired behavior) but then it goes to the original color without fading out. Why does this happen and how to avoid it?
So once again, we have a question where a user provides some code and says why is this happening? The answer in this case is the normal answer to these questions:
You wrote some code to make it happen
So to dig a little deeper, you have asked:
if you write a letter you see it flash correctly to the color set in the animation and go back. But if you click several times the duration is longer (which is the desired behavior) but then it goes to the original color without fading out. Why does this happen and how to avoid it?
First, why does this happen?
So the reason why it is happening is because you declared a ColorAnimation that has no From value set, so it will always start from the current value, whether this value has been manipulated by an Animation or not:
<ColorAnimation AutoReverse="True" To="#A933FF" Duration="0:0:1" FillBehavior="Stop"
Storyboard.TargetProperty="Foreground.Color" IsCumulative="True" />
For a single character entered, you'll see the ColorAnimation as you expected. However, when you continually type further characters, it will already have reached your set purple colour and you won't see any further animations until you stop typing, because it is now trying to animate from your purple colour to the same purple colour.
Now, how to avoid it?
To fix this issue, either supply a From colour, or set the Duration to be much quicker, or preferably both:
<ColorAnimation AutoReverse="True" From="White" To="#A933FF" Duration="0:0:0.1"
Storyboard.TargetProperty="Foreground.Color" FillBehavior="Stop"
IsCumulative="True" />
You can achieve the desired result using ColorAnimationUsingKeyFrames this will offer you much precise control over the same using keyframes instead of reversing the double animation which loses the initial value after being invoked multiple time and FillBehavior stop force it to revert to original value as result of animation complere
sample for you
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames Duration="0:0:2" Storyboard.TargetProperty="Foreground.Color" >
<SplineColorKeyFrame Value="#A933FF"/>
<SplineColorKeyFrame Value="White" KeyTime="0:0:1"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
more cleaner approach involves stopping the previous storyboard and invoking a new one
<TextBlock Text="{Binding Text, ElementName=Box, NotifyOnTargetUpdated=True}" Foreground="White" >
<TextBlock.Style>
<Style TargetType="TextBlock" >
<Style.Resources>
<Storyboard x:Key="animate">
<ColorAnimationUsingKeyFrames Duration="0:0:2" Storyboard.TargetProperty="Background.Color" >
<SplineColorKeyFrame Value="#A933FF"/>
<SplineColorKeyFrame Value="White" KeyTime="0:0:1"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<StopStoryboard BeginStoryboardName="beginAnimate"/>
<BeginStoryboard x:Name="beginAnimate" Storyboard="{StaticResource animate}"/>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I would like to make WPF Window that contains DataGrid control and enables following scenario in C# WPF DataGrid: Data is loaded in DataGrid, application validates data in background (parallel async operations), when row is determined to be valid its bacground color becomes green, red otherwise. What is cleanest way to program this behaviour? Is there any built-in functionality in DataGrid and WPF to do this kind of validation?
EDIT:
For now I have manged to perform this by using RowStyle, but this makes application non responsive because validation takes time for each row, so I would like to make this async and parallel.
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding BgColor}">
</Setter>
</Style>
</DataGrid.RowStyle>
EDIT2:
Here is progress:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BgColor}" Value="DarkRed">
<Setter Property="Background" Value="DarkRed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Code behind looks like this:
Func<List<bool>> func = () => data.AsParallel().Select(x => File.Exists(x.FullPath)).ToList();
List<bool> res = null;
IAsyncResult ar = func.BeginInvoke(new AsyncCallback(x=>
{
res = ((Func<List<bool>>)((AsyncResult)x).AsyncDelegate).EndInvoke(x);
for (int i = 0; i < res.Count; ++i)
if (!res[i])
data[i].BgColor = Brushes.DarkRed;
}), null);
Remaining problem is that row background color is refreshed only when row is redrawn (moved out of view and than into view again). Any clean and easy way to fix this?
EDIT3:
Finally everything works exactly as required, only thing missing in EDIT2 was to implement INotifyPropertyChanged in data source class.
Well the best approach would be using a DataTrigger in the style of the DataGridItems and provide a property (bool?) in the ViewModel which is bound to the DataTrigger. In the DataTrigger you could declare the visual for all three states Null, True, False
For additional information on DataTrigger, please have a look here.
Edit
Hmm, any chance to put the highlighting functionality in a DataTemplate? I implemented a highlighting for the selection state of an entity. And it works as expected.
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="true">
<!-- Expand -->
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="CommandPanel">
<DoubleAnimation Duration="0:0:0.200" Storyboard.TargetProperty="Opacity" To="1" />
<DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="Height"
To="{StaticResource TargetHeightCommandPanel}" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<!-- Collapse -->
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="CommandPanel">
<DoubleAnimation Duration="0:0:0.100" Storyboard.TargetProperty="Opacity" To="0" />
<DoubleAnimation Duration="0:0:0.150" Storyboard.TargetProperty="Height" To="0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
Btw, have you ever heard of MVVM?
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.