Setting VisualState Triggers in C# in Xamarin and Maui - c#

For reasons not relevant here I need to set VisualState in C# rather than XAML.
However, I don't know how to change VisualState in response to a CollectionView's Selection change.
Here's the working XAML code which I'm trying to replicate in C#:
<CollectionView SelectionMode="Multiple"
ItemsSource="{Binding ViewModel_Contacts}"
SelectionChanged="OnSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="sharedModels:Showuser">
<StackLayout Orientation="Horizontal" HeightRequest="40">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter TargetName="LabelMark" Property="Label.TextColor"
Value="Blue"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter TargetName="LabelMark" Property="Label.TextColor"
Value="Red"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Label x:Name="LabelMark"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The way I have VisualState set in C# is as follows:
VisualStateManager.GetVisualStateGroups(LabelMark).Add(
new VisualStateGroup() {
Name= "CommonStates",
States = {
new VisualState {
Name = "Normal",
Setters = {
new Setter {
Property = Label.TextColorProperty,
Value = Colors.Blue
}
},
StateTriggers= {
new StateTrigger {
/* THIS IS THE QUESTION:
HOW DO I SET IT UP? */
}
}
},
new VisualState {
Name = "Selected",
Setters = {
new Setter {
Property = Label.TextColorProperty,
Value = Colors.Red
}
},
StateTriggers= {
new StateTrigger {
/* THIS IS THE QUESTION:
HOW DO I SET IT UP? */
}
}
}
}
}
);
I don't know how to set up trigger(s) for the above scenario.
It seems obvious that the trigger should be bound to the containing CollectionView's selection changed event, but I don't know how to get to it.
Any help is appreciated. Thanks!

Looking at the Microsoft docs for StateTrigger and there is only one property that you can set. It's IsActive and indicates whether a trigger should be applied. So StateTrigger would look like this.
new StateTrigger {
IsActive= true
}
If you are looking to bound your VisualState to a specific target, you should change your Setters. From the Microsoft docs for Setter Class. There is a property called Target so that you can target that element and apply the Value to it.
It looks like this what done on the XAML side as well, but with the property TargetName which does not exist.
Hope this helps.
Sorry I did look at the wrong Microsoft Docs. I went back and looked at the correct Docs as well as some examples from GitHub. Unfortunately the StateTrigger was only referenced in the XAML code.
This is the code that I was looking at if you are interested.
XAML
ViewModel
That being said I think I managed to work out how its done from trying it out in Visual Studio. You can connect the StateTrigger via a BindingContext to your ViewModel. In your case I'm pretty sure in your case it would be this.
new StateTrigger {
BindingContext = new ViewModel_Contacts()
}
Hope that helps for real this time.

Related

WPF Animation not working on "fast" property change

I am having trouble making an animation work in WPF with DataTriggers when the property binded is changes "fast". The animation is a simple flash of an element. My problem is even after a long time searching on the internet, i can't figure out a way to explain why toggling the property ON and OFF in 2 consecutives lines doesn't work, but if the thread sleeps 1ms it does. I tried inserting other instructions to "waste" some time but it doesn't work either.
Here is the property in question in the viewmodel:
private bool m_activateFlash;
public bool ActivateFlash
{
get { return m_activateFlash; }
set
{
SetPropertyBackingField(ref m_activateFlash, value);
}
}
Here is the XAML :
<DataTemplate x:Key="JobTemplate" DataType="viewModel:JobViewModel">
<Border Margin="8,4,8,4" BorderThickness="1" BorderBrush="{StaticResource BorderBrush}" Background="{StaticResource JobBackgroundBrush}">
<Border.Resources>
<Storyboard x:Key="Blink">
<ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" FillBehavior="Stop"
Duration="0:0:0.4" To="{StaticResource JobBackgroundFlash}" RepeatBehavior="3x" AutoReverse="True"/>
</Storyboard>
</Border.Resources>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding ActivateFlash, NotifyOnTargetUpdated=True}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Blink}" Name="BeginBlinkStoryBoard"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
[....]
And finally here is where it is causing me problems in the viewmodel :
private void TriggerFlash()
{
ActivateFlash = true;
System.Threading.Thread.Sleep(1); // HACK DOESN'T WORK WITHOUT THIS LINE
ActivateFlash = false;
}
I looked up EventTriggers as maybe a way to circumvent the issue, but I feel DataTrigger is the logical way to achieve what i want. Any help is really appreciated :)
EDIT
It appears i wasnt invoking Trigger flash on the main thread. Simply calling Dispatcher.Invoke(...) solved the problem
Thanks
Adam
Yeah, if you do:
ActiveFlash = true;
ActiveFlash = false;
You aren't going to see anything on the screen because the UI is on the main thread and it hasn't had time to paint before the value is changed. Toggling the value happens so fast that you aren't going to see anything on the screen.
I believe and somebody can correct me if I'm wrong, but I believe that WPF batches repaints behind the scenes for performance, so you're just stomping over yourself.

Is it possible for individual listviewitems selected, pointerover and pressed states to be controlled by the data bound item

I'm currently trying to style a ListView so each of the items inside it can control their own background color, PointerOver, Pressed and Selected states.
I have figured out that I can set the Background of the ListViewItem to Transparent and let the DataTemplate for my item control the Background. But what I'm struggling with is the VisualStates of the ListViewItem. They just won't seem to style correctly.
What I've tried so far is so far is putting my Binding code for the colors inside the Style for the ListViewItem like so:
NOTE: Code has been cut down for brevity, ListViewItem style taken from the Generic.xaml
<Style TargetType="ListViewItem">
<Setters.../>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Grid x:Name="ContentBorder">
<!-- ContentPresenter etc in here -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Pressed">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding AccentColour}" />
</ObjectAnimationUsingKeyFrames>
</VisualState>
<!-- More Visual States-->
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However this doesn't work and I assume it has something to do with the ListViewItem not knowing the DataContext for the Binding.
My item is just a basic class to hold info like the text associated with the item and the colors the for Background and the States.
public class BasicItem {
// Constructor etc
public SolidColorBrush AccentColour { get; set; }
// More Colours
}
Another approach I had considered was creating AttachedProperties for the Colors on the ListViewItem. Only issue is I'm not sure how I would bind to them.
Anyone have any idea if it's possible for the Item to control its States in this way, any help appreciated.
Another approach I had considered was creating AttachedProperties for
the Colors on the ListViewItem. Only issue is I'm not sure how I would
bind to them.
You are right about using AttachedProperties and this is how it can be done.
Step 1. Define your attached properties
public static Brush GetPointerOverBackground(DependencyObject obj)
{
return (Brush)obj.GetValue(PointerOverBackgroundProperty);
}
public static void SetPointerOverBackground(DependencyObject obj, Brush value)
{
obj.SetValue(PointerOverBackgroundProperty, value);
}
public static readonly DependencyProperty PointerOverBackgroundProperty =
DependencyProperty.RegisterAttached("PointerOverBackground", typeof(Brush), typeof(YourStaticClass), new PropertyMetadata(null));
Step 2. Define default values for your attached properties
<Setter Property="local:YourStaticClass.PointerOverBackground" Value="LightBlue" />
Step 3. Use attached properties in your Visual States
<VisualState x:Name="PointerOver">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{TemplateBinding local:YourStaticClass.PointerOverBackground}" />
</ObjectAnimationUsingKeyFrames>
</VisualState>
Step 4. Create an Interface that defines all the brushes
The reason of doing this is so we can later cast the object (i.e. BasicItem) bound to each template to this Interface, without the need of knowing its concrete type. Of course your BasicItem should implement this Interface.
public interface ISupportListViewItemBrushes
{
SolidColorBrush PointerOverBrush { get; set; }
Step 5. Create a Behavior responsible for assigning Brushes to ListViewItems
This is where everything comes together. First we will need to install the XAML Behavior Nuget Package. After that, create a Behavior called SupportListViewItemBrushesBehavior and make it only attachable to a ListView.
public class SupportListViewItemBrushesBehavior : Behavior<ListView>
ListView has an event called ContainerContentChanging where we have access to each ListViewItem and its underlying Item (i.e. BasicItem/ISupportListViewItemBrushes) object. Finally, we just use the SetAttachedPropertyName method to assign the corresponding Brush to the ListViewItem like below
private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (!args.InRecycleQueue &&
args.ItemContainer is ListViewItem container &&
args.Item is ISupportListViewItemBrushes brushes)
{
Helper.SetPointerOverBackground(container, brushes.PointerOverBrush);
}
}
That's all!
Note we had to create the Behavior here because UWP doesn't support FindAncestor binding yet. Alternatively, rather than using attached properties and a Behavior, you can extend ListView/ListViewItem to achieve the same result.
I have included a small sample here for your reference. And here's the end result. Hope this helps!
Implement a StyleSelector that chooses the ListViewItem style you want for a given item, and set it as the ItemContainerStyleSelector on your ListView.

Custom Control Storyboard Template Binding Not Working - XAML C#

I created a custom Control which has some Dependency Properties which values I also Need to assign to the storyboard in the ControlTemplate.
Here is the beginning of the Code of my Control Template:
<ControlTemplate TargetType="local:RingPresenter">
<Grid x:Name="RootGrid">
<Grid.Resources>
<Storyboard x:Key="StatisticUpdateAnnimation">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(RingSlice.EndAngle)" Storyboard.TargetName="ringSlice">
<EasingDoubleKeyFrame KeyTime="0" Value="45"/>
<EasingDoubleKeyFrame KeyTime="0:0:2.2" Value="{TemplateBinding Angle}">
The Important part is the Value={TemplateBinding Angle}" - the value is getting applied succesfully to my
<helper:RingSlice InnerRadius="100" Radius="150" StartAngle="0" EndAngle="{TemplateBinding Angle}" Fill="DarkCyan" x:Name="ringSlice">
</helper:RingSlice>
... which is part of my Control Template, but in the storyboard the value is staying 0. The Debugger is saying that Anglehas the correct value and it is working fine for my Ringslice and even the metadata should be 45.
Why is this value not applied to my storyboard? How can I fix this?
When in doubt, swap EndAngle="{TemplateBinding Angle}" for EndAngle="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Angle}".
TemplateBinding is quick and useful but doesn't always have the strongest capacity to feed through information it's bound to, for reasons that escape me entirely.

Property Value Animation triggered by a custom DependencyProperty in UserControl?

Question Prelude :
How can I animate the Angle Property of a RotateTransform of
an UIElement A when the value of a Custom DependencyProperty
of type boolean becomes True when I click on an
UIElement B, all inside an UserControl ?
And in XAML ONLY (or mostly) ? if possible :)
I've written all the following to provide all the required details of my issue. You can stop reading from top to bottom anytime; even directly jump to the actual question, which is within the first quarter of the post.
Context :
The question is about Animation Triggers and Custom Property Binding, all within a single UserControl. No Window involved so far.
To begin with, let's assume I created an UserControl, which has a main Grid that contains two other Grids. Simpliest schemas :
<!-- MyControl.xaml -->
<UserControl ...blahblahblah>
<Grid>
<Grid x:Name="TiltingGrid">
<!-- This Grid contains UIElements that I want to tilt alltogether -->
</Grid>
<Grid>
<Ellipse x:Name="TiltingTrigger" ...blahblahblah>
<!-- This Ellipse is my "click-able" area -->
</Ellipse>
</Grid>
</Grid>
</UserControl>
Then, in Code Behind, I have a DependencyProperty called IsTilting.
// MyControl.xaml.cs
public bool IsTilting
{
// Default value is : false
get { return (bool)this.GetValue(IsTiltingProperty); }
set { this.SetValue(IsTiltingProperty, value); }
}
private static readonly DependencyProperty IsTiltingProperty =
DependencyProperty.Register(
"IsTilting",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(
false,
new PropertyChangedCallback(OnIsTiltingPropertyChanged)));
private static void OnIsTiltingPropertyChanged(...) { ... }
// .. is a classic Callback which calls
// private void OnIsTiltingChanged((bool)e.NewValue)
// and/or
// protected virtual void OnIsTiltingChanged(e) ...
Then, I defined some Properties for my Grid named TiltingGrid in the XAML :
<Grid x:Name="TiltingGrid"
RenderTransformOrigin="0.3, 0.5">
<Grid.RenderTransform>
<RotateTransform
x:Name="TiltRotate" Angle="0.0" />
<!-- Angle is the Property I want to animate... -->
</Grid.RenderTransform>
<!-- This Grid contains UIElements -->
<Path ... />
<Path ... />
<Ellipse ... />
</Grid>
And I would like to trigger the tilting upon clicking on a specific area inside this UserControl : An Ellipse, in the secund Grid :
<Grid>
<Ellipse x:Name="TiltingTrigger"
... Fill and Stroke goes here ...
MouseLeftButtonDown="TryTilt_MouseLeftButtonDown"
MouseLeftButtonUp="TryTilt_MouseLeftButtonUp">
</Ellipse>
</Grid>
If I'm not mistaken, Ellipse doesn't have a Click Event, so I had to create two EventHandlers for MouseLeftButtonDown and MouseLeftButtonUp. I had to do it that way to be able to :
Make the Ellipse capture Mouse upon MouseLeftButtonDown, and set a private field to true
Test whether the Mouse Point is inside the Ellipse upon MouseLeftButtonUp, set the value of the private field to false, then Release the Mouse.
Invert the value of the DependencyProperty IsTilting (true/false) if something looking like a "Click" occurs (..which would trigger the tilting animation if I'm able to resolve the appropriate Binding..)
I'll save you the MouseLeftDown/Up code, but I can provide it if required. What they do is to change the value of the DP.
Issue(s) :
I don't know how to trigger the Angle Animation when my DependencyProperty is updated. Well. That's not an actual issue, it's a lack of knowledge I reckon :
I don't know how to capture a custom event to be used with <EventTrigger>
I don't know how and where to trigger a StoryBoard using a True/False DependencyProperty.
And the actual question is :
From now on, how do I declare the code that makes the Angle
Property of the RotateTransform to animate from 0.0 to
45.0 (Rendering Transform of my Grid "TiltingGrid") when my DP IsTilting is set to true, and animate back to 0.0
when it's False ?
mostly in XAML way ..?
I do have a working code in C# code behind (detailed below) What I'm looking for is a workable solution in XAML (because it's usually very easy to rewrite almost anything in CodeBehind when you know how to do it in XAML)
What I tried so far...
From now on, you don't have to read further unless you absolutely want to know all the details...
1) Triggering the animation using natively defined Ellipse EventTriggers works only for Events defined for this specific UIElement (Enter/Leave/MouseLeftDown...) Done that alot with many UIElements.
But those triggers are not the ones I need : My Grid should tilt based on an On/Off or True/False custom state in a DP, not when something like a Mouse activity occurs.
<Ellipse.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TiltRotate"
Storyboard.TargetProperty="Angle"
From="0.0" To="45.0"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
...
</Ellipse.Triggers>
When the mouse enters the Ellipse, my Grid is tilting accordingly, but hence, How do I have access to custom Events defined in my UserControl ?
2) Then, based on the above scheme, I supposed I just had to create a Routed Event on my MyControl Class, or two, actually :
TiltingActivated
TiltingDisabled
.
public static readonly RoutedEvent TiltingActivatedEvent =
EventManager.RegisterRoutedEvent(
"TiltingActivated",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(EventHandler));
public event RoutedEventHandler TiltingActivated
{
add { AddHandler(MyControl.TiltingActivatedEvent, value); }
remove { RemoveHandler(MyControl.TiltingActivatedEvent, value); }
}
private void RaiseTiltingActivatedEvent()
{
RoutedEventArgs newEventArgs =
new RoutedEventArgs(MyControl.TiltingActivatedEvent, this);
RaiseEvent(newEventArgs);
}
Then, I'm calling RaiseTiltingActivatedEvent() in one method called by my IsTilting DependencyProperty Callback when its new value is true, and RaiseTiltingDisabledEvent() when its new value is false.
Note : IsTilting value is changed to either true of false upon Ellipse "Click", and the two events are fired accordingly. But there's a problem : it's not the Ellipse that fires the Events, but the UserControl itself.
Anyway, I tried to replace the <EventTrigger RoutedEvent="UIElement.MouseEnter"> with the followings :
Attempt one :
<EventTrigger RoutedEvent="
{Binding ic:MyControl.TiltingActivated,
ElementName=ThisUserControl}">
.. and I get :
"System.Windows.Markup.XamlParseException: (...)"
"A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
I'm assuming I cannot bind to an Event ?
Attempt two :
<EventTrigger RoutedEvent="ic:MyControl.TiltingActivated">
.. and I get :
"System.NotSupportedException:"
"cannot convert RoutedEventConverter from system.string"
I'm assuming the RoutedEvent name cannot be resolved ? Anyway, this approach make me drift far from my initial goal : Trigger a DoubleAnimation when a custom Property changes (because in more complex scenarios, wouldn't it be easier to trigger different animations and call specific methods, all in CodeBehind when we can have dozens of different values, than creating lengthy and tricky XAML things ? Best would be learning how to do both of course. I'm eager to know)
3) Then I came across this article : Beginner's WPF Animation Tutorial.
A Code Behind Animation Creation. That's the thing I wanted to learn after knowing how to do it in XAML. Anyway, let's have a try.
a) Create two Animation Properties (private), one for tilting animate and another for tilting animate back.
private DoubleAnimation p_TiltingPlay = null;
private DoubleAnimation TiltingPlay
{
get {
if (p_TiltingPlay == null) {
p_TiltingPlay =
new DoubleAnimation(
0.0, 45.0, new Duration(TimeSpan.FromSeconds(0.2)));
}
return p_TiltingPlay;
}
}
// Similar thing for TiltingReverse Property...
b) Subscribe to the two events then set the Angle Animation of our RotateTransform live at runtime in code behind :
private void MyControl_TiltingActivated(object source, EventArgs e)
{
TiltRotate.BeginAnimation(
RotateTransform.AngleProperty, TiltingPlay);
}
// Same thing for MyControl_TiltingDisabled(...)
// Subscribe to the two events in constructor...
public MyControl()
{
InitializeComponent();
this.TiltingActivated +=
new RoutedEventHandler(MyControl_TiltingActivated);
this.TiltingDisabled +=
new RoutedEventHandler(MyControl_TiltingDisabled);
}
Basically, when I "click" (MouseButtonLeftDown + Up) on the Ellipse :
Mouse hit spot is resolved
if within the Ellipse area, change DP IsTilting to not IsTilting.
IsTilting then fires either TiltingActivated or TiltingDisabled.
Both are captured, then the related tilting animation (private properties) is activated on the named <RotateTransform ..> of the Grid.
And it works !!!
I said it would be very easy in code behind ! (lengthy code .. yes, but it works) Hopefully, with snippets templates, it's not that boring.
But I still don't know how to do it in XAML. :/
4) Since my custom events seems to be out of scope in the XAML side, what about <Style> ? Usually, binding in a Style is like breathing. But honestly, I don't know where to begin.
the animation target is the Angle Property of a <RotateTransform /> applied to a Grid.
the binded Dep. Property IsTilting is a custom DP of MyControl, not UserControl.
and one Ellipse drives the updating of the DP.
let's try something like <RotateTransform.Style>
<RotateTransform ...>
<RotateTransform.st...>
</RotateTransform>
<!-- such thing does not exists -->
or RotateTransform.Triggers ? ... doesn't exist either.
UPDATE :
This approach works by declaring the Style in the Grid to animate, as explained in Clemens's answer. To resolve the custom
UserControl Property binding, I just had to use
RelativeSource={RelativeSource AncestorType=UserControl}}. And to
"target" the Angle Property of the RotateTransform, I just had to use
RenderTransform.Angle.
What else ?
I often see samples that sets the DataContext to something like "self". I don't really understand what's a DataContext, but I'm assuming it makes all Path resolving point to the declared Class by default, for Bindings. I already used that in one UserControl which solved my issue, but I didn't dig deeper to understand the how and why. Perhaps this could help resolve capturing custom Events in code behind directly from the XAML side ?
One XAML mostly way I'm nearly sure will work is :
to create a custom UserControl for that Ellipse, say, EllipseButton, with its own Events and Properties
then, embed that in MyControl UserControl.
Capture the TiltingActivated Event of the EllipseButton to trigger the DoubleAnimation in a Storyboard of the EllipseButton, just like it could be done for the Click event of a Button.
That would work fine, but I find it hacky to create and embed another control just to be able to access the appropriate custom event. MyControl is not a SuperWonderfulMegaTop project that would require such surgery. I'm sure I'm missing something soooooooo obvious; can't believe something that simple outside the WPF world can't be even simplier in WPF.
Anyway, such cross-connections are highly subject to memory leaks (perhaps not the case here, but I try to avoid that whenever possible...)
Perhaps defining <Grid.Style> or alike would do the trick ... but I don't know how. I only know how to use <Setter>. I don't know how to create EventTriggers in a Style declaration. UPDATE : Explained by Clemens's answer.
This SO question (Fire trigger in UserControl based on DependencyProperty) suggests to create a Style in UserControl.Resources. Tried the following... It doesn't work (and there is no animation there anyway - I don't know how to declare animation in Style yet)
.
<Style TargetType="RotateTransform">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsTilting, ElementName=ThisUserControl}" Value="True">
<Setter Property="Angle" Value="45.0" />
</DataTrigger>
</Style.Triggers>
</Style>
This SO question (Binding on RotateTransform Angle in DataTemplate not taking effect) has a lot of unknown knowledge to me to be understandable. However, assuming the suggested workaround works, I don't see anywhere something looking like an animation. Just a binding to a value that is not animated. I don't think the Angle animates itself magically.
In Code Behind like the working code above, I could create another DependencyProperty called GridAngle (double), then bind the Angle Property of RotateTransform to that new DP, then animate that DP directly ??? Worth a try, but at a later time : I'm tired.
Just found that my Registered Events are of Bubble Strategy. This would matter if the Event is to be captured by some parent containers, but I want to handle everything directly inside the UserControl, not like on this SO question. However, Tunneling strategy - that I don't understand yet - may play a role : would Tunneling allows my Ellipse to capture the Events of my UserControl ? Have to read the documentation again and again because it's still very obscure to me... What bugs me now is that I am still unable to use my custom events in this UserControl :/
What about a CommandBinding ? That seems very interresting, but it's a whole different chapter to learn. It seems to involve a lot of code behind, and since I already have a working code behind (which looks more readable to me...)
In this SO question (WPF Data Triggers and Story Boards), the accepted answer seems to only work if I'm animating a property of an UI Element that can have a UIElement.Style definition. RotateTransform doesn't have such ability.
Another answer suggest the use of ContentControl, ControlTemplate... Just like CommandBinding above, I haven't dig deep enough to understand how I could adapt that to my UserControl.
However, those answers seems the ones that mostly fit my needs, expecially that ContentControl way. I'll have some tries at a later time, and see if it solves the XAML mostly way of implementing the desired behaviour. :)
And last, this SO question (EventTrigger bind to event from DataContext) suggest the use of Blend/Interactivity. The approach looks nice, but I don't have Blend SDK and not really willing to unless I absolutely have to... Again : another whole Chapter to eat... :/
Side note :
As you would have guessed, I'm a beginner in WPF/XAML (I know it's not an excuse) which I started to learn a few weeks ago. I'm kind of "the whole stuff would be very easy to do in WinForms right now..." but perhaps you could help me figure out how easy it would be to achieve it in WPF :)
I've searched alot (I know it's not an excuse either) but I have no luck for this time. - Okay, I've just read three dozens of articles, code projects and SO topics, and the MSDN documentation about triggers, animations, routed events.. just seems to polish the surface without digging deep in the core (seems like MS think inheriting from Button is the way to solve almost anything...)
Long question, short answer. Use Visual States:
<UserControl ...>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="TiltedState">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TiltingGrid"
Storyboard.TargetProperty="RenderTransform.Angle"
To="45" Duration="0:0:0.2"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TiltingGrid" RenderTransformOrigin="0.3, 0.5">
<Grid.RenderTransform>
<RotateTransform/>
</Grid.RenderTransform>
...
</Grid>
</Grid>
</UserControl>
Whenever an appropriate condition is met, call
VisualStateManager.GoToState(this, "TiltedState", true);
in the UserControl's code behind. This may of course also be called in the PropertyChangedCallback of a dependency property.
Without using Visual States, you might create a Style for your TiltingGrid which uses a DataTrigger with a Binding to your UserControl's IsTilted property:
<Grid x:Name="TiltingGrid" RenderTransformOrigin="0.3, 0.5">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTilted,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
To="45" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
To="0" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RenderTransform>
<RotateTransform/>
</Grid.RenderTransform>
...
</Grid>

Datatrigger Not Firing Storyboard Or Not At All

I can not seem to get Data triggers to work no matter what I do. I'm trying to play a storyboard from the view model when a value changes yet nothing happens, no error, no cant find binding, just nothing... The code I'm currently attempting to get working is:
XAML:
<Page
DataContext="{Binding ViewModel, Source={StaticResource Locator}}">
<Page.Resources>
<DataTrigger x:Key="alertInDataTrigger" Binding="{Binding alert}" Value="1">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource alertIn}" x:Name="alertIn_start"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="alertIn_start" />
</DataTrigger.ExitActions>
</DataTrigger>
<Storyboard x:Key="alertIn" Changed="visible" >
<ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="alert">
<EasingThicknessKeyFrame KeyTime="0:0:0.3" Value="729,2,2,658"/>
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
<\Page.Resources>
C#:
public int alert
{
get { return this._alert; }
set
{
if (this._alert != value)
{
int oldalert = this._alert;
this._alert = value;
RaisePropertyChanged("alert");
}
}
}
I can see the int alert properly changing to 1 and I know the pages bindings are working as all the other bindings on the page are properly showing their bindings yet I can not seem to get data triggers to work. I could use a little help on this stumper :/
I see two things you are doing wrong. One is that triggers are should be added to the Page.Triggers not Page.Resources. The trigger will never fire if it is in the resources.
The other is Storyboard.TargetName="alert". This should not be set to alert unless you have an element in the Page that is named alert. I believe that if you don't add Story.TargetName then it defaults to the current element which is the page. Otherwise, set the name of the element you want to animate its margin.

Categories

Resources