How to pass StaticResources to a ViewModel in MVVM? - c#

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");

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.

c# wpf animating opacity in visibility trigger

I am creating a Style containing Animation's, which can then be inherited to specific control styles, which are applied automatically.
Actually I'm struggling on implementing a simple Animation:
When Visibility is changed to Visible, Opacity is changed from 0 to 1
When Visibility is changed to something else than visible, Opacity is doing the inverse thing
So far I got:
<Style x:Key="BaseAnimationsStyle">
<Style.Triggers>
<Trigger Property="FrameworkElement.Visibility" Value="Visible">
<Trigger.EnterActions> <!-- this works -->
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions><!-- this doesn't -->
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
If the above Style is set on a control it then has the following behaviour:
When the control's Visibility is set to Visible the transition is working correctly, meaning it is fading in.
Issue:
When the control's Visibility is set to Hidden (or even Collapsed), the control will be hidden instantly without fading.
My guess is, that there is some default behaviour to be overridden of how FrameworkElement's deal with Visibility-Changes.
Setting the Visibility property to Collapsed or Hidden will make the element invisible right away but instead of setting the Visibility property you could set some attached property of yours and then animate the Opacity property to fade out the element. Please refer to the following link for more information and an example.
WPF: How To Animate Visibility Property?: http://blogs.microsoft.co.il/arik/2010/02/08/wpf-how-to-animate-visibility-property/ https://www.codeproject.com/Articles/57175/WPF-How-To-Animate-Visibility-Property
WPF Fade Animation
I have solved my issue with the information provided by #mm8's Answer.
Basically I added the VisibilityAnimation Class to my Project. I then simply created my Base-Style using the Provided Attached-Property inside a Setter.
<!-- Animations -->
<Style x:Key="BaseAnimationsStyle">
<Setter Property="anim:VisibilityAnimation.AnimationType" Value="Fade" />
</Style>
<!-- This adds the Visibility Animation to every Grid which has access to this resource -->
<Style TargetType="{x:Type Grid}" BasedOn="{StaticResource BaseAnimationsStyle}" />
Another Solution:
Create AttachedProperties for Visiblity. This enables you to set the Visibility of any control without having the Opacity set automatically.
If you want to use Bindings on the property, the first evaluation of the binding causes an animation, if the value is not Visibility.Visible. This is why another Property is needed, to specify another Visibility to start.
public static class AnimateableVisibility
{
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.RegisterAttached(
"Visibility", typeof(Visibility), typeof(AnimateableVisibility), new PropertyMetadata(default(Visibility), VisibilityPropertyChanged));
private static void VisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var val = (Visibility) e.NewValue;
// Set StartVisibility to Visible when Visibility is set to Visible
if (val == Visibility.Visible)
d.SetCurrentValue(StartVisibilityProperty, val);
}
public static readonly DependencyProperty StartVisibilityProperty = DependencyProperty.RegisterAttached(
"StartVisibility", typeof(Visibility), typeof(AnimateableVisibility), new PropertyMetadata(default(Visibility)));
public static Visibility GetVisibility(DependencyObject obj)
{
return (Visibility)obj.GetValue(VisibilityProperty);
}
public static void SetVisibility(DependencyObject obj, Visibility value)
{
obj.SetValue(VisibilityProperty, value);
}
public static Visibility GetStartVisibility(DependencyObject obj)
{
return (Visibility)obj.GetValue(VisibilityProperty);
}
public static void SetStartVisibility(DependencyObject obj, Visibility value)
{
obj.SetValue(VisibilityProperty, value);
}
}
Now you can use those properties as follows:
<Grid>
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" Orientation="Horizontal">
<Button Margin="5,0,15,0" Padding="7,0" Style="{StaticResource VisibilityAnimation}" utils:AnimateableVisibility.StartVisibility="Hidden"
utils:AnimateableVisibility.Visibility="{Binding ElementName=CheckBox, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}">I'm a Button</Button>
<CheckBox VerticalAlignment="Center" IsChecked="False" x:Name="CheckBox"></CheckBox>
</StackPanel>
</Grid>
By changing StartVisibility to Visible, you can see how the Button fades out on startup.
all thats missing now is the applied Style:
<Style x:Key="VisibilityAnimation">
<Style.Triggers>
<Trigger Property="utils:AnimateableVisibility.StartVisibility" Value="Hidden">
<!-- This avoids the Animation in cases when the first evaluation of AnimateableVisibility.Visibility is false -->
<Setter Property="UIElement.Visibility" Value="Hidden" />
</Trigger>
<Trigger Property="utils:AnimateableVisibility.Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation To="1" Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation To="0" Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>

TriggerAction object must be associated with one and only one trigger object

I defined BeginStoryboard object in resources in style.
In same style definition I would like use this BeginStoryboard object in 2 event triggers which fire animation.
It is possible that 2 different event triggers will use same object or I must define 2 different BeginStoryboard objects?
<Style x:Key="SerialPoster" TargetType="Border">
<Style.Resources>
<BeginStoryboard x:Key="SerialPosterBeginStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
From="0"
To="1"
Duration="0:0:2"
AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
From="0"
To="1"
Duration="0:0:2"
AccelerationRatio="1" />
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
From="70"
To="0"
Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</Style.Resources>
<!-- TriggerAction object must be associated with one and only one trigger object. -->
<Style.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<EventTrigger.Actions>
<StaticResource ResourceKey="SerialPosterBeginStoryBoard"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Border.MouseEnter">
<EventTrigger.Actions>
<StaticResource ResourceKey="SerialPosterBeginStoryBoard" />
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
Yes, it is possible to reference resource and so to reuse the resource instance at different places.
No need to place the Storyboard inside Style.Resource just place it inside Window.

DataGrid async validation

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?

c# animate the Alignment - possible?

is it possible to create a c# animation for the AttachedPropertys like Alignment? Maybe 1 Second Move between the Change from HorizontalAlignment.Left to HorizontalAlignment.Right - is it possible?
Thanks a lot.
You can't animate it in the sense of producing a smooth animation where something slides from left to right since they are discrete states. There aren't any in-between values. It is possible to create an "animation" which changes the alignment at some point from left to right, it just won't slide across. You could also do a lot of work and measure all the controls to manually create an animation which moves something from one side of the screen to the other using things like Canvas.Left or margins to position the controls.
The attached property part is not an issue, just use the full name of the attached property in the target property part of your animation.
While it's not possible to directly animate smoothly between two properties like
HorizontalAlignment="Right" VerticalAlignment="Bottom"
to
HorizontalAlignment="Center" VerticalAlignment="Center"
I did come up with a way to do this for an app in a way that I thought might be worth sharing. I simply placed the control in a grid that took up the full pane of the window. I aligned the control to the bottom right of the grid. Then I animated the grid with to transform and scale the corner down that I need to the point I want to align the control too (center in this example. See the complete code below.
<Grid Margin="5,5,14,70" Visibility="{Binding Path=AdminModeIsEnabled, Converter={StaticResource CollapsedVisibilityConverter}, FallbackValue=Visible}">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding CenterPanel}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To=".5" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="2" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To=".5" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
<DoubleAnimation Storyboard.TargetProperty="(RenderTransform).(ScaleTransform.ScaleX)" To="1" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Height="15" Source="\Images\Test.png" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
</Grid>
It is possible, here is an example:
class StoryBoardManager : System.Windows.Media.Animation.Storyboard
{
public void ChangeRectangleAlignment(DependencyObject target, VerticalAlignment verticalAlignment, HorizontalAlignment horizontalAlignment, int BeginTimeMillisecond)
{
ObjectAnimationUsingKeyFrames objectAnimation = new ObjectAnimationUsingKeyFrames()
{
BeginTime = TimeSpan.FromMilliseconds(0)
};
Storyboard.SetTarget(objectAnimation, target);
Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("(FrameworkElement.HorizontalAlignment)"));
DiscreteObjectKeyFrame keyFrame = new DiscreteObjectKeyFrame(horizontalAlignment, TimeSpan.FromMilliseconds(BeginTimeMillisecond));
objectAnimation.KeyFrames.Add(keyFrame);
this.Children.Add(objectAnimation);
}
}
For more information, see this other question.

Categories