My application has a few background worker, each doing different work. When I click the 'Start' button, all backgroundworker will start simultaneously.
in my xaml, I had defined my animation of a rotate image:
<window.Resources>
<Storyboard x:Key="imageRotate">
<DoubleAnimation Storyboard.TargetName="transRotate"
Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
From="0" To="360"
Duration="0:0:0.5"
AutoReverse="False"
RepeatBehavior="Forever"/>
</Storyboard>
</window.Resources>
I want the animation to begin when all the backgroundworker started, and stop ONLY after ALL background worker stopped.
I have a property call AreWorkersBusy:
private bool _areWorkerBusy;
public bool AreWorkerBusy
{
get
{
return _areWorkerBusy;
}
set
{
bool isBusy = false;
foreach(BackgroundWorker worker in BackgroundWorkerList)
{
if(worker.IsBusy)
isBusy = true;
}
_areWorkerBusy = isBusy;
}
}
but it's not dependencyProperty, so I can't bind to my animation's DataTrigger.
Any workaround???
Help!
One of the possible ways,
You can implement INotifyPropertyChanged to notify 'AreWorkerBusy' changes, create a dependency property in the control and bind 'AreWorkerBusy' with it.
Create two routed events one to start animation and another to stop animation.
In the property changed handler for your DP, raise the specific routed event.
In your control write event triggers, and based on the event start of stop the animation.
I had written a similar experience, http://keepitsimpleengineer.blogspot.com/2010/09/wpf-circular-progress-control-part-2.html
Related
I subscribed to the wpf window's Loaded event: Loaded += loaded; and try to change the opacity of some controls in code behind.
I notice that in the method loaded the controls are not painted by wpf yet. So the code has no effect, the rendering of the controls occurs only after the method is exited.
1) Is there another event e.g. Rendered that I can subscribe to?
EDIT: I just discovered that there is an OnContentRendered event and the following code works:
Although an animation is probably preferrable.
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
for (int i = 0; i < 100; i++)
{
Parentpanel.Opacity += 0.01;
Splashscreen.Opacity -= 0.01;
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
Thread.Sleep(50);
}
}
Otherwise I probably have to use an animation that changes the opacity of usercontrol1 from 0.1 to 1.0 and of usercontrol2 from 1.0 to 0.0.
2) Do you know an example for such an animation?
In your Loaded handler you can post the UI altering operation ( e.g. void ChangeOpacity() ) on the dispatcher:
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ChangeOpacity));
It will execute after the rendering is done.
Edit
I see you simply need an animation to start when the window opens. It's easily done in XAML, here's a working example generated in Blend:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="200">
<Window.Resources>
<Storyboard x:Key="myStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="myControl">
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource myStoryboard}"/>
</EventTrigger>
</Window.Triggers>
<StackPanel>
<TextBox x:Name="myControl" Text="I'm disappearing..." />
</StackPanel>
</Window>
I just recently was having issues while trying to render some standardized animation to WPF usercontrols when the visibility changed. In my app, I have a couple singleton static classes. In one, I added a static method "VisibleFader" and you pass in the framework element control and it automatically attaches the event handler to the double-animation against the opacity property. It works great and does not require ANY changes to any other Styles, Control Templates, or any other theme implementations.
public static DoubleAnimation da;
public static void VisibleFader(FrameworkElement fe)
{
if (da == null)
{
da = new DoubleAnimation();
da.From = 0;
da.To = 1;
da.Duration = new Duration(TimeSpan.FromSeconds(.7));
}
fe.IsVisibleChanged += myFader;
}
private static void myFader(object sender, DependencyPropertyChangedEventArgs e)
{
((FrameworkElement)sender).BeginAnimation(FrameworkElement.OpacityProperty, da);
}
Then, in my class (such as your Loaded Event), I just call this static method with that "userControl" object.
MySingletonClass.VisibleFader( this.whateverUserControl );
Done... So, when the visibility changes, it fades IN from nothing to 1. And if something was having its visibilty to hidden, it's gone anyhow.
I have a a custom Panel that raises a RoutedEvent which is defined globally in a static class :
public class CompressItemsToFitStackPanel : StackPanel
{
protected override Size ArrangeOverride(Size arrangeSize)
{
// some logic
// Raise Attached Event
CustomEventManager.RaiseArrangeEvent(this);
return base.ArrangeOverride(arrangeSize);
}
}
My attached Event:
public static class CustomEventManager
{
public static readonly RoutedEvent ArrangeEvent = EventManager.RegisterRoutedEvent("Arrange",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(CustomEventManager));
internal static void RaiseArrangeEvent(UIElement target)
{
var args = new RoutedEventArgs(ArrangeEvent);
target.RaiseEvent(args);
}
}
this Panel is the items panel for an Items control ,
the ItemsTemplate as an EventTrigger which i wan't to fire when the attached event is raised:
<DataTemplate DataType="{x:Type local:Checker}" x:Key="CheckerTempalte">
<Ellipse x:Name="ellipse" Style="{StaticResource checkerStyle}">
<Ellipse.Triggers>
<EventTrigger RoutedEvent="local:CustomEventManager.Arrange">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)"
Storyboard.TargetName="ellipse"
>
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="{Binding Val}" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
The Event trigger is not triggered ,
Maybe i'm not using AttachedEvents correctly or it is not Declared correctly
i need the event from the panel to propagate and trigger the EventTriggers in the Child elements ,
any ideas what i'm doing wrong ?
EDIT :
After dkozl's insight i came to the conclusion that i need an AddXXXHandler and RemoveXXXHandler in order for the XAML to add/ remove the handler for the EventTrigger
public static void AddArrangeHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(CustomEventManager.ArrangeEvent, handler);
}
}
public static void RemoveArrangeHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(CustomEventManager.ArrangeEvent, handler);
}
}
but still nothing happens , and i never reach these methods .
EDIT 2 :
thanks to dkozl's comments below ,
the Event is raised for each child element , since he the ellipses are down the
Visual Tree .
protected override Size ArrangeOverride(Size arrangeSize)
{
foreach (UIElement child in children)
{
CustomEventManager.RaiseArrangeEvent(child);
}
}
but still nothing happens , iv'e also tested the 'StoryBoard' by giving the 'EventTrigger'
the 'MouseEnter' event and moving my mouse over the element , it works fine.
still , even raising the event on each Ellipse still does not work ...
any ideas ?
Another point of interest is that the child elements are actually the 'Checker' type
and not the Ellipse which the DataTemplate represents , i don't get how 'Checker' is considered a UIElement .?
What you've created is standard RoutedEvent, not an attached one. Add/Remove handler is different for attached events. You'll need two separate methods (AddArrangeChildrenHandler and RemoveArrangeChildrenHandler). It has been explained on MSDN site
UPDATE:
I've copied updated Ellipse definition and CustomEventManager class into my application, added test button which calls CustomEventManager.RaiseArrangeEvent(ellipse); when clicked and it works for me.
I also had to add Ellipse.RenderTransorm of TransformGroup, fourth transformation being TranslateTransform to make it work like in the example
UPDATE2: Event is raised on the panel where the Ellipse is placed which means that bubbling event would never reach it as it will start from the panel and go up the visual tree to Window never reaching children of the panel
You should maybe consider a code only approach to this problem. If you wish to define the animations in XAML you can possibly StaticResource them into properties on the CompressToFitStackPanel. But if your DataTemplate is referring to a specific transform by index you are definitely losing the power and abstraction of DataTemplate'ing.
Reevaluate your problem space and what the design is supposed to solve. Use ILSpy or Reflector to analyze how other Panels solved problems with properties or Attached Dependency Properties (Grid.Column, Grid.Row).
Place more of the burden on the new layout panel and less on those who use it.
Good luck.
There's something fundamental about VisualStateGroups that I'm not understanding. Everything I've read has led me to believe that they are orthogonal. That is, a state change in one group won't affect other groups. Indeed, they would be rather pointless if this were not the case.
However, in an attempt to understand some odd behavior I was encountering, I put together a simple example that shows that a state change in one group can trigger an animation in another. I'm trying to understand how this can be.
All I want is a ToggleButton-based control to have one appearance when toggled (IsChecked == true) regardless of whether it has focus or whether the mouse is over it, for example.
Firstly, I have a very simple control that transitions between custom states in a custom group:
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
[TemplateVisualState(Name = normalState, GroupName = activationGroupName)]
[TemplateVisualState(Name = hoverState, GroupName = activationGroupName)]
[TemplateVisualState(Name = activatedState, GroupName = activationGroupName)]
public class CustomControl : ToggleButton
{
private const string activationGroupName = "Activation";
private const string normalState = "Normal";
private const string hoverState = "Hover";
private const string activatedState = "Activated";
public CustomControl()
{
this.DefaultStyleKey = typeof(CustomControl);
this.Checked += delegate
{
this.UpdateStates();
};
this.Unchecked += delegate
{
this.UpdateStates();
};
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.UpdateStates();
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
this.UpdateStates();
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
this.UpdateStates();
}
private void UpdateStates()
{
var state = normalState;
if (this.IsChecked.HasValue && this.IsChecked.Value)
{
state = activatedState;
}
else if (this.IsMouseOver)
{
state = hoverState;
}
VisualStateManager.GoToState(this, state, true);
}
}
Secondly, I have a template for this control that changes the background color based on the current state:
<Style TargetType="local:CustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Activation">
<VisualStateGroup.Transitions>
<VisualTransition To="Normal" GeneratedDuration="00:00:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="Hover">
<Storyboard>
<ColorAnimation Duration="00:00:0.2" To="Red" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Activated">
<Storyboard>
<ColorAnimation Duration="00:00:0.2" To="Blue" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="grid" Background="White">
<TextBlock>ToggleButton that should be white normally, red on hover, and blue when checked</TextBlock>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
For the most part it works, but when the control loses focus it transitions back to the normal state.
I can get around this by explicitly handling focus changes in my control and enacting a state update:
public CustomControl()
{
this.DefaultStyleKey = typeof(CustomControl);
this.Checked += delegate
{
this.UpdateStates();
};
this.Unchecked += delegate
{
this.UpdateStates();
};
// explicitly handle focus changes
this.GotFocus += delegate
{
this.UpdateStates();
};
this.LostFocus += delegate
{
this.UpdateStates();
};
}
However, it makes no sense to me why I have to do this.
Why is a state change in one VisualStateGroup causing an animation defined by another to execute? And what is the simplest, most correct way for me to achieve my stated goal?
Thanks
Note how the GotoState method simply takes the state name without any qualification as to which group it belongs to. Whilst the groups separate the set of states that a control can hold at any one time (one state per group) the state names are expected to be unique across all states in the manager.
Note also that the ToggleButton from which you derive has the following attribute associated with the class:-
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
This indicates that the logic you have inherit will at some point call GotoState with the state "Normal". Typically controls only have one UpdateStates method (as you have) and at any point where any of the states may have changed it will be called. This function in turn will call GotoState multiple times with one state name from each group. Hence at the point when the control loses focus the ToggleButton calls GotoState to set the "Unfocused" state but along with that will also call it with "Normal" and either "Checked" or "Unchecked".
Now you've got your own template where the states "Unfocused", "Checked" are not present so those calls to GotoState from ToggleButton don't do anything. However you do have "Normal". The VSM has no way of knowing that the ToggleButton use of "Normal" is intended to choose from its default template's "CommonStates" group. Instead it just finds a VisualState with the name "Normal" which in this case is found in your "Activiation" group. Hence your existing state of "Activated" is replaced with your "Normal".
Change your "Normal" to "Blah" and things work as you expect.
(It helps to remember that the group names don't really do a lot despite their presence in the TemplateVisualStateAttribute, their only use I can think of is in the Blend UI)
I'm not hugely familiar with a lot of WPF yet, so if this query betrays some clear misunderstandings I'd appreciate them being pointed out.
What I'm trying to do (for good or ill) is synchronise a DoubleAnimation.FromProperty with the actual width of a StackPanel containing/running the animation.
I originally tried doing this with data binding, e.g.
BindingOperations.SetBinding(anim,
DoubleAnimation.FromProperty,
new Binding {Source = panel, Path = new PropertyPath(ActualWidthProperty)});
But this doesn't seem to work. Although I can bind a text box to the From property and see that it's changing, the animation continues to run from the initial value. Is this anything to do with story board freezing? (which I know nothing about but have just heard of)
Then I thought why not stop and restart the story board (turn it off and on again!), changing the FromProperty in the meantime, by handling the StackPanel.SizeChanged event. Something like this :
void panel_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (myStoryBoard != null && myStoryBoard.GetCurrentState(panel) == ClockState.Active)
{
myStoryBoard.Stop(panel);
foreach(var child in myStoryBoard.Children)
{
var anim = child as DoubleAnimation;
if (anim == null) continue;
anim.From = panel.ActualWidth;
}
myStoryBoard.Begin(panel, true);
}
}
This works, but, of course, it starts the animation from scratch each time the panel is resized. What I'd prefer is to be able to resume the animation from the point it was interrupted but with a new FromProperty. Is this even possible?
It is a lot easier than you think. The cool thing about Animations in WPF and Silverlight is that they can be relative to the current situation. The only thing you need to do to create a relative animation is creating for example the DoubleAnimation with To filled in but do NOT fill in the From.
Example below is to animate the opacity of a stackpanel from the current value to 1:
<DoubleAnimation Duration="0:0: To="1"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="StackPanel.Opacity" />
EDIT1:
Make the animation loop back to its original value without using the from and loop forever
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<DoubleAnimation Duration="0:0:2"
To="1"
Storyboard.TargetName="LayoutRoot"
Storyboard.TargetProperty="StackPanel.Opacity"
/>
</Storyboard>
Is it possible to have nested visual states. What I mean is if a ParentControl has a ChildControl and both have their own visual states, is it possible by setting the state of the ParentControl to change the state of the ChildControl accordingly.
You need to call the GoToState method to change the visual state of the child control.
Since you need to call a method you can't use Storyboards in visual state manager of a parent control since these can only animate properties.
Hence you need to write some code in the child control. To monitor the state of the parent and respond appropriately.
There are a number of different ways to do this but the crucial nugget of info is to use the VisualStateManager.GetVisualStateGroups method to find the VisualStateGroup on the parent that you are interested in, then attach to that group's CurrentStateChanging event. Hence code in the Child control can be notified when a state its interested in is being transitioned to by the parent and can call GoToState appropriately against itself.
I'm just going to declare a new dependency property:
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State",
typeof( string ),
typeof( TextBlockControl ),
new PropertyMetadata("Top",
new PropertyChangedCallback(StateChanged)));
[Category("DigItOut"), Description("State")]
public string State
{
get
{
return this.GetValue(StateProperty).ToString();
}
set
{
this.SetValue(StateProperty, value);
}
}
private static void StateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (!String.IsNullOrEmpty(args.NewValue.ToString()))
VisualStateManager.GoToState(sender as TextBlockControl, args.NewValue.ToString(), true);
}
And then just set it from it's parents state:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="States">
<VisualState x:Name="Reverse">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="textBlockControl" Storyboard.TargetProperty="(TextBlockControl.State)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<System:String>Bottom</System:String>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Straight"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
But if I still want control over the use of transitions, then I'll have to find another solution. Probably second property.