DataGrid async validation - c#

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?

Related

Skip storyboard cleanly in ControlTemplate

I have a Style for a ToggleButton which defines a ControlTemplate. My ToggleButton is animated when it changes states, but i don't want it to animate when i navigate to a new page. So, i added an EventTrigger on the Loaded event with SkipStoryboardToFill to avoid this behavior, and it does what i want.
My only issue now, is that when i add a new ToggleButton, it tries to skip storyboards which haven't been started, generating an Animation Warning ("Unable to perform action because the specified Storyboard was never applied to this object for interactive control.") which seems to impact my application's performance.
I could probably work around that but i'd rather solve the actual problem. Is there a way i could add a condition in my EventTrigger ?
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Loaded">
<SkipStoryboardToFill BeginStoryboardName="checkedSB" />
<SkipStoryboardToFill BeginStoryboardName="uncheckedSB" />
</EventTrigger>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}"
Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin">
<ThicknessAnimation To="20 1 2 1" Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="uncheckedSB">
<Storyboard Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin">
<ThicknessAnimation To="2 1 2 1" Duration="0:0:0.1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
Is there a way i could add a condition in my EventTrigger ?
Short answer: No.
An EventTrigger always applies when the corresponding event is being raised.
If you want to trigger the animations conditionally, you should either switch to using a MultiDataTrigger or implement the animations programmatically.

WPF change button hover style programmatically

I want to create a custom style for buttons in my WPF application. I want to be able to change their Backgroound and BorderThickness properties when mouse is over them.
Some of the buttons are created dynamically in c# so I don't think that xaml solution will be enough.
I tried achieving my goal using Triggers and Setters in c#, but I couldn't get it to work, hover color always stayed light blue.
Here is how I'm creating buttons:
Button but = new Button()
{
Height = 40,
Background = new SolidColorBrush(Colors.Gray),
Content = new Label() {
FontSize = 13,
FontWeight = FontWeights.Medium,
Foreground = new SolidColorBrush(Colors.White)
}
}
stackPanel.Children.Add(but)
I know this is not the best way to create controls and I could probably be better off using MVVM, but I can't change the whole application so I'm just looking for a c# solution.
Thanks!
EDIT:
I added a style to App.xaml file. It looks like this (UpperMenuModeButton is my control based on regular button):
<Application.Resources>
<Style TargetType="UpperMenuModeButton" x:Key="UpperMenuModeButton" >
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
I am setting the style while creating the button like this:
this.Style = (Style) Resources["UpperMenuModeButton"];
I am still getting regular light blue background on hover.
You can create a general style like this:
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetProperty="MaxHeight"
To="90" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:1"
Storyboard.TargetProperty="MaxHeight" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style>
Put this code in the scope of your buttons (in it's page or grid or container) and it'll work automatically.
For more info see here
Edit
For your problem regarding button hover events see here
For that style to work on a normal button you need to set the TargetType to Button. Otherwise it will not be applied. Hope this helps.

How to reuse style with two different bindings?

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.

XAML - Synchronize same animation on different rows of a DataGrid

I'm using a DataGrid to show some elements of a collection:
<DataGrid Name="grdItems" ItemsSource="{Binding Path=myItemsList}" [...]
what I want is that if a particular column of a Row is '1', that row starts to blink. I can achieve this behaviour in this way:
<DataGrid ... >
<DataGrid.Resources>
<Storyboard x:Key="rowBlink" x:Name="Blink" AutoReverse="True" RepeatBehavior="Forever" Timeline.DesiredFrameRate="40" SpeedRatio="1">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="00:00:01" Value="Orange" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</DataGrid.Resources>
</DataGrid>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Acked}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="blinkStoryBoard" Storyboard="{StaticResource rowBlink}" />
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding Acked}" Value="True">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="blinkStoryBoard" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
The problem is that the animation are not in synch for each row of the grid. If a row is added later, it will blink not in phase with the others. Is there a way to synch the animation?
Not necessarily a solution to your exact question, but potentially a workaround that will achieve your intended effect:
Instead of having each row blink on its own, maybe you can have the background of the DataGrid blink? Those rows which shouldn't be animated can have their BackgroundColor set to your default color, and the animated rows can have their background set to Transparent (so you can see the background).
There may be a few issues, such as having edges of the DataGrid showing the BackgroundColor animation as it blinks, but it may be worth a try. I'd run it myself, but I don't have my environment set up on this machine...if I get a chance tonight I'll run a test and post here again.

How to pass StaticResources to a ViewModel in MVVM?

My application (MVVM Light) resizes it's main window (hides and shows it with an animation). For the animation I use a DataTrigger with parameters from StaticResources:
<Window.Resources>
<system:Double x:Key="WindowMaxWidth">400</system:Double>
<system:Double x:Key="WindowMinWidth">25</system:Double>
</Window.Resources>
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding DropBox.IsShown}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="{StaticResource WindowMaxWidth}"
Duration="0:0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="{StaticResource WindowMinWidth}"
Duration="0:0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
In my ViewModel I need my window's width value, so I bound it. The problem is that it's 0 by default, so I have to initialize it with a value. Actually what need is the value form my static resources: WindowMaxWidth.
I can't move the value of WindowMaxWidth to ViewModel because DataTriggr doesn't accept bindings (it complains about threads)
I don't want to keep the same value separately in StaticResources and ViewModel to avoid incoherence.
What should I do?
Put WindowMaxWidth and WindowMinWidth in your viewmodel and reference them with x:Static:
namespace MyNamespace
{
class ViewModel
{
public static double WindowMaxWidth = 400;
public static double WindowMinWidth = 25;
}
}
Import the right namespace xmlns:myns="clr-namespace:MyNamespace"
<DoubleAnimation Storyboard.TargetProperty="Width"
To="{x:Static myns:ViewModel.WindowMaxWidth}"
Duration="0:0:0:0.2"/>
You can use code behind in such way (for instance in constructor, after you set DataContext to ViewModel):
(this.DataContext as MyViewModel).MyWindowWidth = (double)this.FindResource("WindowMaxWidth");

Categories