XAML Storyboard causing infinite loop. Attempting to animate Progressbar - xaml

Need some help understanding XAML Storyboard animations. I'm attempting to create a countdown animation in a XAML Storyboard using a ProgressBar control. The animation should cause the progress bar to count down from it's maximum value to it's minimum value over a given duration. Here is the relevant XAML:
<ProgressBar x:Name="PollCountdown" Orientation="Vertical" Width="20" Value="{Binding PollValue}" Maximum="{Binding PollMax}" Minimum="{Binding PollMin}" ValueChanged="ProgressBar_ValueChanged">
<ProgressBar.Triggers>
<EventTrigger RoutedEvent="ProgressBar.ValueChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="PollCountdown" Storyboard.TargetProperty="Value"
From="{Binding PollMax}" To="{Binding PollMin}" Duration="{Binding Interval, Converter={StaticResource IntToDurationCovnerter}}">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ProgressBar.Triggers>
</ProgressBar>
...and here is the code for the for the ProgressBar_ValueChanged event. The purpose of this is just to allow me to view how the value is changing, and will eventually be removed from the XAML code:
private void ProgressBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var x = (sender as ProgressBar).Value;
if (x == 0)
e.Handled = true;
}
Unfortunately, something is causing an infinite loop error to occur when I trigger the animation. By capturing the value_changed event I'm able to monitor the changes in the Value property. What I see occurring is the bar's value decreases by 0.0041 on the first change, but then on the next change, resets back to the bar's Maximum property. Any clues as to what could be causing this infinite loop?

Here is the solution I ultimately came up with. The progressbar will begin/end the countdown with the Run property is set to true/false. The Duration property determines how long the countdown will take.
<ctrl:CountdownBar x:Name="Countdown" Orientation="Vertical" Run="{Binding RunPoll}" VerticalAlignment="Top" Height="121" Margin="0,1,0,-6" Grid.RowSpan="2" HorizontalAlignment="Left" Width="10" >
<ctrl:CountdownBar.Triggers>
<EventTrigger RoutedEvent="ctrl:CountdownBar.Restarting">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Countdown" Storyboard.TargetProperty="Value" Duration="{Binding PollDuration}" From="100" To="0">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ctrl:CountdownBar.Triggers>
</ctrl:CountdownBar>
And the CountdownBar Control:
public class CountdownBar : ProgressBar
{
public static DependencyProperty IsRunningProperty = DependencyProperty.Register("IsRunning", typeof(bool), typeof(CountdownBar), new PropertyMetadata(false));
public static DependencyProperty DurationProperty = DependencyProperty.Register("Duration", typeof(Duration), typeof(CountdownBar), new PropertyMetadata(new Duration(TimeSpan.FromSeconds(10))));
public static DependencyProperty RunProperty = DependencyProperty.Register("Run", typeof(bool), typeof(CountdownBar), new PropertyMetadata(false));
public static RoutedEvent RestartCountdownEvent = EventManager.RegisterRoutedEvent("Restarting", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CountdownBar));
public CountdownBar()
{
this.Maximum = 100;
this.Minimum = 0;
this.Value = 100;
this.ValueChanged += Bar_ValueChanged;
}
~CountdownBar()
{
this.ValueChanged -= Bar_ValueChanged;
}
public bool Run
{
get { return ((bool)GetValue(RunProperty)); }
set
{
if (value)
Start();
else
Stop();
SetValue(RunProperty, value);
}
}
public bool IsRunning
{
get { return ((bool)GetValue(IsRunningProperty)); }
set { SetValue(IsRunningProperty, value); }
}
public Duration Duration
{
get { return ((Duration)GetValue(DurationProperty)); }
set { SetValue(DurationProperty, value); }
}
public void Start()
{
IsRunning = true;
this.Value = this.Minimum;
if (Value == 0)
RaiseRestartCountdown();
}
public void Stop()
{
IsRunning = false;
this.Value = this.Maximum;
}
public event RoutedEventHandler Restarting
{
add { AddHandler(RestartCountdownEvent, value); }
remove { RemoveHandler(RestartCountdownEvent, value); }
}
public void RaiseRestartCountdown()
{
RoutedEventArgs args = new RoutedEventArgs(RestartCountdownEvent);
RaiseEvent(args);
}
private void Bar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (IsRunning)
if (this.Value == this.Minimum)
if (this.Maximum > this.Minimum)
RaiseRestartCountdown();
}
}
Originally I wanted to make this a completely lookless control, but sometimes you just have to go with what works. If anyone has any ideas to eliminate the c# code and move everything to XAML, please upload a solution.

Related

Xamarin Forms: Not able to update the custom control on the UI

I have created a custom control that has horizontal bar there can be N number of boxview in the bar based on number I get from API for example i have a control with limit of 20 controls and i have selected 10 of them so 10 boxview will be on orange and remaining 10 from 20 will be black
i need to update control on every checkbox selection and it is working fine for me till i go to next screen and come back to the same screen once i come back to this screen control stops updating wile debug i get data in my code but its not updating on UI.
How i use it in my code
<views:CustomDashControl
x:Name="customDash"
Grid.Row="2"
Grid.ColumnSpan="3"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding ControlData}"
VerticalOptions="Start" />
My CustomControl XAML
<ContentView
x:Class="LIRAppV1.Controls.CustomDashControl"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<StackLayout
Padding="1"
BackgroundColor="White"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand">
<!--<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>-->
<Grid
x:Name="progressbar"
BackgroundColor="Transparent"
ColumnSpacing="0"
HorizontalOptions="FillAndExpand"
VerticalOptions="Start">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
</Grid.RowDefinitions>
</Grid>
</StackLayout>
</ContentView>
My CodeBehind of custom control
public partial class CustomDashControl : ContentView
{
static int totalCount;
static Color barColor;
private static ObservableCollection<CustomControlData> tempData;
public static ObservableCollection<CustomControlData> TempData
{
get { return tempData; }
set
{
tempData = value;
customDashControl.OnPropertyChanged("TempData");
}
}
static CustomDashControl customDashControl;
public CustomDashControl()
{
try
{
InitializeComponent();
customDashControl = this;
}
catch (Exception ex)
{
BaseViewModel.HandleException(ex);
}
}
#region Bindable Properties
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create("ItemsSource",
typeof(ObservableCollection<CustomControlData>),
typeof(CustomDashControl), new
ObservableCollection<CustomControlData>(),
BindingMode.Default, null, OnItemsSourceChanged);
private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue != newValue)
{
(bindable as CustomDashControl).OnPropertyChanged("ItemsSource");
TempData = (ObservableCollection<CustomControlData>)newValue;
UpdateBar();
}
}
private static void UpdateBar()
{
try
{
if (TempData != null && TempData.Count > 0)
{
var control = customDashControl.progressbar;
control.ColumnDefinitions.Clear();
control.Children.Clear();
//Working
for (int i = 0, j = 0; i < TempData.Count; i++, j++)
{
control.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
var tempBox = new BoxView() { Color = TempData[i].BarColor, WidthRequest = TempData[i].BarWidth };
control.Children.Add(tempBox, j, 0);
j++;
control.ColumnDefinitions.Add(new ColumnDefinition() { Width = 1 });
control.Children.Add(new BoxView() { Color = Color.White, WidthRequest = 1 }, (j), 0);
}
Console.WriteLine(control.Children);
}
}
catch (Exception ex)
{
BaseViewModel.HandleException(ex);
}
}
public ObservableCollection<CustomControlData> ItemsSource
{
get { return (ObservableCollection<CustomControlData>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly BindableProperty TotalCountProperty = BindableProperty.Create(propertyName: "TotalCount", returnType: typeof(int), declaringType: typeof(CustomDashControl), defaultValue: 0, propertyChanged: propertyChanges1);
private static void propertyChanges1(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue != newValue)
{
var control = bindable as CustomDashControl;
totalCount = (int)newValue;
if (totalCount > 0)
{
(customDashControl.BindingContext as CustomDashControlViewModel).totalCount = totalCount;
}
}
}
public int TotalCount
{
get { return (int)GetValue(TotalCountProperty); }
set { SetValue(TotalCountProperty, value); }
}
#endregion
}
OnAppearing will be executed before the screen comes, when you want an Loaded event that needs to be executed right after the screen comes, there is a workaround.
Create an property in viewmodel like below.
private bool _toggleTemp;
public bool ToggleTemp
{
get => _toggleTemp;
set => SetProperty(ref _toggleTemp, value);
}
Add the following line to the last line of the constructor.
LoadingVm.ToggleTemp = true;
Add an Switch to your screen and make IsVisible to false as below.
<Switch IsToggled="{Binding ToggleTemp}" Toggled="Switch_OnToggled" IsVisible="False" />
Now you can write the code that you want to write in Page Loaded in Switch_OnToggled.
private async void Switch_OnToggled(object sender, ToggledEventArgs e)
{
/* Your code goes here... */
}
Thanks!
If this does not work refer this --
In the onappearing I wrapped everything in an async call and waited a few hundred ms for the page to get rendered.
await WaitAndExecute(1000, () =>
{
// refresh code
}
protected async Task WaitAndExecute(int milisec, Action actionToExecute)
{
await Task.Delay(milisec);
actionToExecute();
}
https://forums.xamarin.com/discussion/22561/page-loaded-event

User Control Animation Only Works On Last Declared Control Instance

I am proto-typing a (Windows 10 universal app) UI and have built a very simple/rough user control to act as a 'badge', i.e show a numeric value in a circle, and animate the value changing. My issue is the control works if there is only a single instance of it in an application page. If there are multiple instances (even if the other instances are invisible), then only the last declared instance animates.
I've tried declaring the animation both in the user control's XAML, and in code behind to attempt to ensure there's no cross-over/mix up with the animation being shared. I also added a change callback to the property being animated, which writes the property value out using Debug.WriteLine. For control instances that animate properly the value changes as expected, i.e if we go from 10 to 20, the property is set to 10, 11, 12, 13.... 20. For the instances that don't work, the value is just set to the from property every time, i.e 10, 10, 10, 10, 10.
Below is a sample of the user control, and then a sample page that uses three instances of it. Placing both of these is a new Windows 10 Universal app called App3 should reproduce the issue. In the sample page the first two badges don't animate properly when their button is clicked, but the last one does.
Is anyone able to point out what I'm doing wrong, and why this breaks with multiple instances on a page?
Thanks.
Note: The code has gotten quite rough as I've hacked things around trying to figure out what the issue is, and it was only prototype code to begin with, so I apologize for the mess.
<UserControl
x:Class="App3.BadgeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="20"
d:DesignWidth="20">
<Grid>
<Ellipse x:Name="Border" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{Binding BadgeBorderBrush}" />
<Ellipse x:Name="BadgeInner" Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{Binding BadgeFillBrush}" />
<TextBlock x:Name="BadgeValue" Margin="5" HorizontalAlignment="Center" FontSize="10" VerticalAlignment="Center" TextAlignment="Center" TextTrimming="CharacterEllipsis" Foreground="White" Text="{Binding DisplayValue}" />
</Grid>
public sealed partial class BadgeView : UserControl
{
public DependencyProperty BadgeBorderBrushProperty = DependencyProperty.Register("BadgeBorderBrush", typeof(Brush), typeof(BadgeView), new PropertyMetadata(new SolidColorBrush(Windows.UI.Colors.Yellow)));
public DependencyProperty BadgeFillBrushProperty = DependencyProperty.Register("BadgeFillBrush", typeof(Brush), typeof(BadgeView), new PropertyMetadata(new SolidColorBrush(Windows.UI.Colors.Orange)));
public DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(BadgeView), new PropertyMetadata(0, new PropertyChangedCallback(ValueChanged)));
public DependencyProperty DisplayValueProperty = DependencyProperty.Register("DisplayValue", typeof(int), typeof(BadgeView), new PropertyMetadata(0, DisplayValueChanged));
private static void DisplayValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(((BadgeView)d).DisplayValue);
}
private Storyboard AnimateBadgeValueCount;
private DoubleAnimation BadgeValueAnimation;
public BadgeView()
{
this.InitializeComponent();
this.BadgeValue.DataContext = this.BadgeInner.DataContext = this.Border.DataContext = this;
AnimateBadgeValueCount = new Storyboard(); ;
AnimateBadgeValueCount.Duration = TimeSpan.FromSeconds(0.5);
Storyboard.AllowDependentAnimations = true;
BadgeValueAnimation = new DoubleAnimation();
BadgeValueAnimation.Duration = TimeSpan.FromSeconds(0.5);
BadgeValueAnimation.EnableDependentAnimation = true;
BadgeValueAnimation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
this.AnimateBadgeValueCount.FillBehavior = FillBehavior.Stop;
this.BadgeValueAnimation.FillBehavior = FillBehavior.Stop;
AnimateBadgeValueCount.Children.Add(BadgeValueAnimation);
Storyboard.SetTarget(AnimateBadgeValueCount, this);
Storyboard.SetTargetProperty(AnimateBadgeValueCount, "DisplayValue");
this.AnimateBadgeValueCount.Completed += AnimateBadgeValueCount_Completed;
}
private void AnimateBadgeValueCount_Completed(object sender, object e)
{
this.DisplayValue = this.Value;
}
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var badgeView = (BadgeView)d;
badgeView.AnimateValue();
}
private void AnimateValue()
{
if (Value != DisplayValue)
{
this.BadgeValue.DataContext = this.BadgeInner.DataContext = this.Border.DataContext = this;
this.AnimateBadgeValueCount.Stop();
this.BadgeValueAnimation.From = this.DisplayValue;
this.BadgeValueAnimation.To = this.Value;
this.BadgeValueAnimation.FillBehavior = FillBehavior.Stop;
//Storyboard.SetTarget(this.AnimateBadgeValueCount, this);
//Storyboard.SetTargetProperty(this.AnimateBadgeValueCount, "DisplayValue");
this.AnimateBadgeValueCount.Begin();
}
}
public Brush BadgeBorderBrush
{
get { return (Brush)this.GetValue(this.BadgeBorderBrushProperty); }
set
{
this.SetValue(this.BadgeBorderBrushProperty, value);
}
}
public Brush BadgeFillBrush
{
get { return (Brush)this.GetValue(this.BadgeFillBrushProperty); }
set
{
this.SetValue(this.BadgeFillBrushProperty, value);
}
}
public int Value
{
get { return (int)this.GetValue(ValueProperty); }
set
{
this.SetValue(ValueProperty, value);
}
}
public int DisplayValue
{
get { return (int)this.GetValue(DisplayValueProperty); }
set
{
this.SetValue(DisplayValueProperty, value);
}
}
}
<Page
x:Class="App3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Orientation="Vertical">
<Button Content="Do it" x:Name="DoIt1" Click="DoIt1_Click" />
<local:BadgeView x:Name="Badge1" Width="20" Height="20" BadgeFillBrush="Blue" />
<Button Content="Do it" x:Name="DoIt2" Click="DoIt2_Click" />
<local:BadgeView x:Name="Badge2" Width="20" Height="20" />
<Button Content="Do it" x:Name="DoIt3" Click="DoIt3_Click" />
<local:BadgeView x:Name="Badge3" Width="20" Height="20" />
</StackPanel>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void DoIt1_Click(object sender, RoutedEventArgs e)
{
this.Badge1.Value += 10;
}
private void DoIt2_Click(object sender, RoutedEventArgs e)
{
this.Badge2.Value += 10;
}
private void DoIt3_Click(object sender, RoutedEventArgs e)
{
this.Badge3.Value += 10;
}
}
What I have done is simplify code and simplify and move the datacontext inside the loaded event inside the constructor:
this.Loaded += (s, e) =>
{
this.DataContext = this;
AnimateBadgeValueCount = new Storyboard(); ;
AnimateBadgeValueCount.Duration = TimeSpan.FromSeconds(0.5);
BadgeValueAnimation = new DoubleAnimation();
BadgeValueAnimation.Duration = TimeSpan.FromSeconds(0.5);
//BadgeValueAnimation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
AnimateBadgeValueCount.Children.Add(BadgeValueAnimation);
Storyboard.SetTarget(AnimateBadgeValueCount, this);
Storyboard.SetTargetProperty(AnimateBadgeValueCount, "DisplayValue");
this.AnimateBadgeValueCount.Completed += AnimateBadgeValueCount_Completed;
};
private async void AnimateValue()
{
if (Value != DisplayValue)
{
this.AnimateBadgeValueCount.Stop();
this.BadgeValueAnimation.From = this.DisplayValue;
this.BadgeValueAnimation.To = this.Value;
BadgeValueAnimation.EnableDependentAnimation = true;
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
this.AnimateBadgeValueCount.Begin();
});
}
}
I have commented the EasingFunction, it works but in my opinion fit betters.
It is curious, because if I set the datacontext just in the constructor it works bad, but inside goes fine.
Tell me if you try.

Is this the efficient way to display list of timers constantly updating the UI?

I am displaying a list of timers which constantly update the UI (every one second) to show proper time.
Is this the efficient way ? How this process can be improved from performance point of view ?
I am using MVVMLight toolkit for windows phone.
XAML code:
<ListBox ItemsSource="{Binding TimersCollection}"
ItemTemplate="{StaticResource SingleItemTemplate}"/>
Here is my simple itemtemplate code,It also has Pause, Add Minute button but removed from here for simplicity:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="SingleItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CurrentTime.Hours}"/>
<TextBlock Text="H"></TextBlock>
<TextBlock Text="{Binding CurrentTime.Minutes}" />
<TextBlock Text="M"></TextBlock>
<TextBlock Text="{Binding CurrentTime.Seconds}" />
<TextBlock Text="S"></TextBlock>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
Here is my ViewModel code which is injected in view:
public class Page1VM : ViewModelBase
{
private ObservableCollection<MyTimer> _timersCollection = new ObservableCollection<MyTimer>();
public Page1VM()
{
// sample code to simulate collection of timers
for (int i = 1; i < 5; i++)
{
var t = new MyTimer();
t.TotalTimeSpan = new TimeSpan(0, i, 0);
_timersCollection.Add(t);
t.Start();
}
}
public IList<MyTimer> TimersCollection
{
get { return _ghatikatimerscoll; }
}
}
Here is ITimer Interace
public interface ITimer
{
bool Start();
bool Stop();
bool IsRunning { get; set; }
void AddMinute();
}
Its implementation
public class MyTimer : ViewModelBase, ITimer
{
public TimeSpan TotalTimeSpan { private get; set; }
private readonly DispatcherTimer _myDispatcherTimer;
private TimeSpan _startTime;
public bool IsRunning { get; set; }
public MyTimer()
{
_myDispatcherTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
_myDispatcherTimer.Tick += _myDispatcherTimer_Tick;
}
private TimeSpan _currentTime;
public TimeSpan CurrentTime
{
get
{
return _currentTime;
}
set
{
_currentTime = value;
RaisePropertyChanged("CurrentTime");
}
}
void _myDispatcherTimer_Tick(object sender, EventArgs e)
{
if (_myDispatcherTimer.IsEnabled)
{
var currenttime = TotalTimeSpan.Add(new TimeSpan(0, 0, 1)) - (DateTime.Now.TimeOfDay - _startTime);
CurrentTime = currenttime;
}
}
public bool Start()
{
_startTime = DateTime.Now.TimeOfDay;
if (_currentTime.TotalSeconds != 0)
{
// resuming after paused
TotalTimeSpan = CurrentTime;
}
IsRunning = true;
_myDispatcherTimer.Start();
return true;
}
public bool Stop()
{
_myDispatcherTimer.Stop();
IsRunning = false;
return true;
}
public void AddMinute()
{
TotalTimeSpan = TotalTimeSpan.Add(new TimeSpan(0, 1, 1));
}
}
Basically, I am displaying a collection of Timers on screen which updates itself. Each item in list has its own DispatcherTimer. User an click "Pause" button for each item to pause that particular timer. User can also click on "Add Minute" button which adds 1 minute to the particular item in collection.
Is this method efficient to update the UI constantly?
Rather than having one timer per item, you can declare one global dispatcher timer that will update every item every second (actually, you should set a delay lower than a second or you may see some items skipping a second). It will be much more efficient than using x dispatcher timers. You'd have to put it in the ViewModel and iterate with a foreach loop. You could also create a wrapper around the timer, define an event that will be triggered every second, and have all the items subscribe to that event, but I don't think it's worth the trouble.

How to databind control height to another control's height?

I'm trying to have 2 controls have the same height. Can I do it with XAML only?
If I did something like <Canvas Height="{Binding Height, ElementName=AnotherControl}" /> it doesn't actually do anything and the height goes to zero. The Output panel doesn't complain about any binding errors so AnotherControl.Height really exists. I tried binding to ActualHeight but it doesn't do anything either.
Anything else I missed?
My guess is that you AnotherControl is not explicitly given a Height. Unfortunately, in WinRT (unlike WPF, but the same as Silverlight), ActualWidth and ActualHeight are what are known as "calculated properties". This means that a property changed event doesn't internally get raised when they change. As a result, binding to them is not reliable, and as you've noticed, it wouldn't quite work.
Side note: it may work from time to time, but that is purely because of the timing of the get call the binding framework makes to ActualHeight.
So as it stands, you cannot do it with XAML only. You have to handle the ActualControl.SizeChanged event in code-behind, and set the Height to AnotherControl.ActualHeight explicitly.
As Kshitij Mehta mentioned, binding to ActualHeight and ActualWidth in WinRT isnt reliable. But there is a nice work-around, where you dont have to use the SizeChanged-Event:
Add this class:
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public double ActualHeightValue
{
get { return Element == null ? 0 : Element.ActualHeight; }
}
public double ActualWidthValue
{
get { return Element == null ? 0 : Element.ActualWidth; }
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy),
new PropertyMetadata(null, OnElementPropertyChanged));
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ActualSizePropertyProxy)d).OnElementChanged(e);
}
private void OnElementChanged(DependencyPropertyChangedEventArgs e)
{
FrameworkElement oldElement = (FrameworkElement)e.OldValue;
FrameworkElement newElement = (FrameworkElement)e.NewValue;
newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
if (oldElement != null)
{
oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
}
NotifyPropChange();
}
private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
{
NotifyPropChange();
}
private void NotifyPropChange()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
}
}
}
Place it in the resources:
<UserControl.Resources>
<c:ActualSizePropertyProxy Element="{Binding ElementName=YourElement}" x:Name="proxy" />
</UserControl.Resources>
And bind to its properties:
<TextBlock x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" />
This Question is very old, but here is my solution.
You can use this Code
<!--First Button-->
<Button x:Name="button1" Height="50" Width="100"/>
<!--Second Button-->
<Button x:Name="button2" Height="50" Width="{Binding ElementName=button1, Path=Width}"/>
I've tested it on my Windows / Windows Phone 8.1 Device and it workes great.

WPF Fade Animation

How would I make a control fade in/out when it becomes Visible.
Below is my failed attempt:
<Window x:Class="WadFileTester.Form1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="MyWindow" Title="WAD File SI Checker" Height="386" Width="563" WindowStyle="SingleBorderWindow" DragEnter="Window_DragEnter" DragLeave="Window_DragLeave" DragOver="Window_DragOver" Drop="Window_Drop" AllowDrop="True">
<Window.Resources>
<Style TargetType="ListView" x:Key="animatedList">
<Style.Triggers>
<DataTrigger Binding="{Binding Visibility}" Value="Visible">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:5"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListView Name="listView1" Style="{StaticResource animatedList}" TabIndex="1" Margin="12,41,12,12" Visibility="Hidden">
</ListView>
</Grid>
</Window>
I don't know how to do both animations (fade in and fade out) in pure XAML. But simple fade out can be achieved relatively simple. Replace DataTriggers with Triggers, and remove ExitActions since they makes no sense in Fade out scenario. This is what you will have:
<Style TargetType="FrameworkElement" x:Key="animatedList">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
But hey, don't give up. If you want to support both animations I can suggest small coding behind the XAML. After we do a trick, we will get what you want by adding one line of code in XAML:
<Button Content="Fading button"
x:Name="btn"
loc:VisibilityAnimation.IsActive="True"/>
Every time we change btn.Visibility from Visible to Hidden/Collapsed button will fade out. And every time we change Visibility back the button will fade in. This trick will work with any FrameworkElement (including ListView :) ).
Here is the code of VisibilityAnimation.IsActive attached property:
public class VisibilityAnimation : DependencyObject
{
private const int DURATION_MS = 200;
private static readonly Hashtable _hookedElements = new Hashtable();
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive",
typeof(bool),
typeof(VisibilityAnimation),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsActiveProperty, value);
}
static VisibilityAnimation()
{
UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement),
new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Ignore.
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return;
}
if (GetIsActive(fe))
{
HookVisibilityChanges(fe);
}
else
{
UnHookVisibilityChanges(fe);
}
}
private static void UnHookVisibilityChanges(FrameworkElement fe)
{
if (_hookedElements.Contains(fe))
{
_hookedElements.Remove(fe);
}
}
private static void HookVisibilityChanges(FrameworkElement fe)
{
_hookedElements.Add(fe, false);
}
private static object CoerceVisibility(DependencyObject d, object baseValue)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return baseValue;
}
if (CheckAndUpdateAnimationStartedFlag(fe))
{
return baseValue;
}
// If we get here, it means we have to start fade in or fade out
// animation. In any case return value of this method will be
// Visibility.Visible.
var visibility = (Visibility)baseValue;
var da = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS))
};
da.Completed += (o, e) =>
{
// This will trigger value coercion again
// but CheckAndUpdateAnimationStartedFlag() function will reture true
// this time, and animation will not be triggered.
fe.Visibility = visibility;
// NB: Small problem here. This may and probably will brake
// binding to visibility property.
};
if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
{
da.From = 1.0;
da.To = 0.0;
}
else
{
da.From = 0.0;
da.To = 1.0;
}
fe.BeginAnimation(UIElement.OpacityProperty, da);
return Visibility.Visible;
}
private static bool CheckAndUpdateAnimationStartedFlag(FrameworkElement fe)
{
var hookedElement = _hookedElements.Contains(fe);
if (!hookedElement)
{
return true; // don't need to animate unhooked elements.
}
var animationStarted = (bool) _hookedElements[fe];
_hookedElements[fe] = !animationStarted;
return animationStarted;
}
}
The most important thing here is CoerceVisibility() method. As you can see we do not allow changing this property until fading animation is completed.
This code is neither thread safe nor bug free. Its only intention is to show the direction :). So feel free to improve, edit and get reputation ;).
You can't directly use the Visibility property for a fade out because setting a trigger on it will first Hide/Collapse the control, THEN animate it. So basically you'll got an animation on a collapsed control => nothing.
One "reliable" way would be to introduce a new Dependency Property (attached or not), say IsOpen and setting a property trigger IsOpen=True on it with:
EnterAction:
Make sure Visibility is set to Visible
Fade in the Opacity from 0 to 1
ExitAction:
Visibility set to Visible at keyframe 0 and Collapsed/Hidden at the last Keyframe
Fade out the Opacity from 1 to 0.
Here's an example:
<Style TargetType="{x:Type local:TCMenu}">
<Style.Resources>
<Storyboard x:Key="FadeInMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOutMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsOpen" Value="true">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
I've been coming at this a slightly different way - I've got an extended version of Ray's answer to this question which adds a FadeIn() and FadeOut() extension method to everything that collapses or shows the element as appropriate, then instead of making objects visible I can just call FadeIn() and FadeOut() on them - and it will work an any element without any specific animation code.
public static T FadeFromTo(this UIElement uiElement, double fromOpacity,
double toOpacity, int durationInMilliseconds, bool loopAnimation,
bool showOnStart, bool collapseOnFinish)
{
var timeSpan = TimeSpan.FromMilliseconds(durationInMilliseconds);
var doubleAnimation =
new DoubleAnimation(fromOpacity, toOpacity,
new Duration(timeSpan));
if (loopAnimation)
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
uiElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
if (showOnStart)
{
uiElement.ApplyAnimationClock(UIElement.VisibilityProperty, null);
uiElement.Visibility = Visibility.Visible;
}
if (collapseOnFinish)
{
var keyAnimation = new ObjectAnimationUsingKeyFrames{Duration = new Duration(timeSpan) };
keyAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame(Visibility.Collapsed, KeyTime.FromTimeSpan(timeSpan)));
uiElement.BeginAnimation(UIElement.VisibilityProperty, keyAnimation);
}
return uiElement;
}
public static T FadeIn(this UIElement uiElement, int durationInMilliseconds)
{
return uiElement.FadeFromTo(0, 1, durationInMilliseconds, false, true, false);
}
public static T FadeOut(this UIElement uiElement, int durationInMilliseconds)
{
return uiElement.FadeFromTo(1, 0, durationInMilliseconds, false, false, true);
}
I realize this Question is a bit old, but I have only read it now and I have tweaked the code given by Anvaka. It supports binding to Visibility (only when binding mode is set to TwoWay). It also supports 2 different duration values for FadeIn and FadeOut.
Here is the class:
public class VisibilityAnimation : DependencyObject
{
#region Private Variables
private static HashSet<UIElement> HookedElements = new HashSet<UIElement>();
private static DoubleAnimation FadeAnimation = new DoubleAnimation();
private static bool SurpressEvent;
private static bool Running;
#endregion
#region Attached Dependencies
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(VisibilityAnimation), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null) throw new ArgumentNullException("element");
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null) throw new ArgumentNullException("element");
element.SetValue(IsActiveProperty, value);
}
public static readonly DependencyProperty FadeInDurationProperty = DependencyProperty.RegisterAttached("FadeInDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(0.5));
public static double GetFadeInDuration(UIElement e)
{
if (e == null) throw new ArgumentNullException("element");
return (double)e.GetValue(FadeInDurationProperty);
}
public static void SetFadeInDuration(UIElement e, double value)
{
if (e == null) throw new ArgumentNullException("element");
e.SetValue(FadeInDurationProperty, value);
}
public static readonly DependencyProperty FadeOutDurationProperty = DependencyProperty.RegisterAttached("FadeOutDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(1.0));
public static double GetFadeOutDuration(UIElement e)
{
if (e == null) throw new ArgumentNullException("element");
return (double)e.GetValue(FadeOutDurationProperty);
}
public static void SetFadeOutDuration(UIElement e, double value)
{
if (e == null) throw new ArgumentNullException("element");
e.SetValue(FadeOutDurationProperty, value);
}
#endregion
#region Callbacks
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Ignore.
// We only specified a property changed call-back to be able to set a coercion call-back
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Get the framework element and leave if it is null
var fe = d as FrameworkElement;
if (fe == null) return;
// Hook the element if IsActive is true and unhook the element if it is false
if (GetIsActive(fe)) HookedElements.Add(fe);
else HookedElements.Remove(fe);
}
private static object CoerceVisibility(DependencyObject d, object baseValue)
{
if (SurpressEvent) return baseValue; // Ignore coercion if we set the SurpressEvent flag
var FE = d as FrameworkElement;
if (FE == null || !HookedElements.Contains(FE)) return baseValue; // Leave if the element is null or does not belong to our list of hooked elements
Running = true; // Set the running flag so that an animation does not change the visibility if another animation was started (Changing Visibility before the 1st animation completed)
// If we get here, it means we have to start fade in or fade out animation
// In any case return value of this method will be Visibility.Visible
Visibility NewValue = (Visibility)baseValue; // Get the new value
if (NewValue == Visibility.Visible) FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeInDurationProperty))); // Get the duration that was set for fade in
else FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeOutDurationProperty))); // Get the duration that was set for fade out
// Use an anonymous method to set the Visibility to the new value after the animation completed
FadeAnimation.Completed += (obj, args) =>
{
if (FE.Visibility != NewValue && !Running)
{
SurpressEvent = true; // SuppressEvent flag to skip coercion
FE.Visibility = NewValue;
SurpressEvent = false;
Running = false; // Animation and Visibility change is now complete
}
};
FadeAnimation.To = (NewValue == Visibility.Collapsed || NewValue == Visibility.Hidden) ? 0 : 1; // Set the to value based on Visibility
FE.BeginAnimation(UIElement.OpacityProperty, FadeAnimation); // Start the animation (it will only start after we leave the coercion method)
return Visibility.Visible; // We need to return Visible in order to see the fading take place, otherwise it just sets it to Collapsed/Hidden without showing the animation
}
#endregion
static VisibilityAnimation()
{
// Listen for visibility changes on all elements
UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement), new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
}
}
This is best done using a behavior
class AnimatedVisibilityFadeBehavior : Behavior<Border>
{
public Duration AnimationDuration { get; set; }
public Visibility InitialState { get; set; }
DoubleAnimation m_animationOut;
DoubleAnimation m_animationIn;
protected override void OnAttached()
{
base.OnAttached();
m_animationIn = new DoubleAnimation(1, AnimationDuration, FillBehavior.HoldEnd);
m_animationOut = new DoubleAnimation(0, AnimationDuration, FillBehavior.HoldEnd);
m_animationOut.Completed += (sender, args) =>
{
AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Collapsed);
};
AssociatedObject.SetCurrentValue(Border.VisibilityProperty,
InitialState == Visibility.Collapsed
? Visibility.Collapsed
: Visibility.Visible);
Binding.AddTargetUpdatedHandler(AssociatedObject, Updated);
}
private void Updated(object sender, DataTransferEventArgs e)
{
var value = (Visibility)AssociatedObject.GetValue(Border.VisibilityProperty);
switch (value)
{
case Visibility.Collapsed:
AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Visible);
AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationOut);
break;
case Visibility.Visible:
AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationIn);
break;
}
}
}
This is specifically being applied to a border - I haven't tried a user control but I expect the same applies.
To use it, you need the Blend Interactivity namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And use this markup on the Border that you want the behavior on:
<i:Interaction.Behaviors>
<Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
</i:Interaction.Behaviors>
You'll need to add in the namespace for the behavior class too..
Quite old now, but could you not just chain the DoubleAnimations?
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:5"
/>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:5"
/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
You may want to try AutoReverse property ... though I am not sure if it works the way you want it to.
This is what I found on MSDN :
When a timeline's AutoReverse property is set to true and its RepeatBehavior property causes it to repeat, each forward iteration is followed by a backward iteration. This makes one repetition. For example, a timeline with an AutoReverse value of true with an iteration Count of 2 would play forward once, then backwards, then forwards again, and then backwards again.
I prefer Nock's XAML-only solution. Combined with the Mike Nakis' comment it gave me the perfect solution. I myself was struggling a bit with the DataTrigger. That's why I want to share it here:
<TextBlock x:Name="Text1" Text="Hello World!" FontSize="30" d:Visibility="Visible">
<TextBlock.Style>
<Style>
<Style.Resources>
<Storyboard x:Key="FadeInMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeOutMenu">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<DataTrigger Binding="{Binding IsOpen}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
<Setter Property="TextBlock.Visibility" Value="Collapsed" />
</Style>
</TextBlock.Style>
</TextBlock>
Also do not forget to implement INotifyPropertyChanged for the Property IsOpen in code behind.