Slider "animation" in WPF? - wpf

I have a button and a slider, when I press the button so do I want the slider to tick one step until it reach its maximum value.
However once I click the button, it sleeps a while and then shows the slider at the maximum value, without showing each tick. Why?
Here's my XAML code:
<StackPanel Orientation="Horizontal">
<Button x:Name="AnimationGoButton" Content="Go" />
<Slider x:Name="AnimationSlider" TickFrequency="1" TickPlacement="BottomRight" IsSnapToTickEnabled="True" Width="200" Maximum="20" Value="0" />
</StackPanel>
And here's my code behind:
Private Sub AnimationGoButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles AnimationGoButton.Click
While (Me.AnimationSlider.Value < Me.AnimationSlider.Maximum)
Me.AnimationSlider.Value += 1
System.Threading.Thread.Sleep(100)
End While
End Sub
I have tried to use a dynamic resource, but the result was the same.
XAML:
<Window.Resources>
<sys:Double x:Key="AnimationSliderValue">0</sys:Double>
</Window.Resources>
And then I changed the Value for the slider in XAML to:
Value="{DynamicResource AnimationSliderValue}"
And change the code behind to:
While (Me.AnimationSlider.Value < Me.AnimationSlider.Maximum)
Resources("AnimationSliderValue") += 1
System.Threading.Thread.Sleep(100)
End While
The result was the same. When I press the button the UI doesn't update until it has reached the Maximum value.
How do I create this "animation" I want for the slider?

You can use Storyboard for animations.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="userControl">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Storyboard x:Key="SlideUpAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(RangeBase.Value)" Storyboard.TargetName="slider1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="10"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="SlideDownAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(RangeBase.Value)" Storyboard.TargetName="slider1">
<SplineDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource SlideUpAnimation}"/>
</EventTrigger>
</Window.Triggers>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<Button x:Name="btnSlideDown" Click="btnSlideDown_Click" Content="Slide Down" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Slider Height="23" x:Name="slider1" Width="100" />
<Button x:Name="btnSlideUp" Click="btnSlideUp_Click" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Slide Up" />
</StackPanel>
And then start the storyboards on button clicks:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnSlideUp_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideUpAnimation"));
}
private void btnSlideDown_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideDownAnimation"));
}
}
Note: You need to add the PresentationFramework.dll in your project references in order to access the Storyboard class in code.
Update per comment below
You want to increment the Slider.Value by whole integers only using animations. Since the target value type is Double, the animation calculates and applies double values to the target, based on the animation frame rate. (The animation frame rate is 60 fps by default, but even if you did reduce it, that still may or may not give you even values depending on the beginning value). I don't know of any ways to tell the DoubleAnimation to use even values only. There exists an Int32Animation class but you cannot apply that to Slider.Value which is of type double.
Here's my hacky solution (which I don't quite like): Add a SliderIntValue (Int32) dependency property to the parent (e.g. MainWindow or maybe your viewmodel) and bind it to the Slider.Value using two-way binding. The Binding class will magically take care of the type conversion. Then apply the animations to the SliderIntValue instead of the slider itself:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="userControl">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Storyboard x:Key="SlideUpAnimation">
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="SliderIntValue" Storyboard.TargetName="userControl">
<EasingInt32KeyFrame KeyTime="0:0:1" Value="10"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="SlideDownAnimation">
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="SliderIntValue" Storyboard.TargetName="userControl">
<EasingInt32KeyFrame KeyTime="0:0:1" Value="0"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<Button x:Name="btnSlideDown" Click="btnSlideDown_Click" Content="Slide Down" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Slider Height="23" x:Name="slider1" Width="100" IsSnapToTickEnabled="True" Value="{Binding SliderIntValue, ElementName=userControl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="btnSlideUp" Click="btnSlideUp_Click" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Slide Up" />
<TextBox TextWrapping="Wrap" Text="{Binding Value, ElementName=slider1}" Margin="20,0,0,0"/>
</StackPanel>
And here's the dependency property added to the MainWindow class:
public partial class MainWindow : Window
{
public static readonly DependencyProperty SliderIntValueProperty = DependencyProperty.Register("SliderIntValue",
typeof(int), typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
}
private void btnSlideUp_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideUpAnimation"));
}
private void btnSlideDown_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideDownAnimation"));
}
}

Related

<EventTrigger RoutedEvent="Binding.TargetUpdated"> not firing with ContentTemplateSelector

I'm trying to animate the Background Color of a Border in a DataTemplate for a DataObject when a Child Property of the DataObject changes.
The DataObject is a Class called Test with two Properties, Number and Text.
I have an ObservableCollection of DataObjects called Numbers.
In a Task I update the Number Property at a regular Interval.
<Window
x:Class="WpfAnimationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfAnimationTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="NumberTemplate">
<TextBlock Text="{Binding NotifyOnTargetUpdated=True}" />
</DataTemplate>
<local:ValueTemplateSelector x:Key="TemplateSelector">
<local:ValueTemplateSelector.NumberTemplate>
<DataTemplate>
<ContentControl Content="{Binding NotifyOnTargetUpdated=True}" ContentTemplate="{StaticResource NumberTemplate}" />
</DataTemplate>
</local:ValueTemplateSelector.NumberTemplate>
</local:ValueTemplateSelector>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder" Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock
Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl
Width="50"
Margin="10"
Content="{Binding Number}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
<!--
ContentTemplate="{StaticResource NumberTemplate}"
-->
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation
FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
namespace WpfAnimationTest
{
public class Locator
{
public Locator()
{
Main = new Main();
}
public Main Main { get; set; }
}
public class Main
{
public ObservableCollection<Test> Numbers { get; set; } = new ObservableCollection<Test>();
public Main()
{
var rnd = new Random(42);
for (int i = 0; i < 10; i++)
{
Numbers.Add(new Test(){Number = i, Text = $"#: {i}"});
}
Task.Run(() =>
{
while (true)
{
try
{
Application.Current?.Dispatcher.Invoke(() =>
{
Numbers[rnd.Next(9)] = new Test
{
Number = rnd.Next(30),
Text = $"# {rnd.Next(30) + 30}"
};
});
Thread.Sleep(1000);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
});
}
}
public class Test
{
public int Number { get; set; }
public string Text { get; set; }
}
public class ValueTemplateSelector : DataTemplateSelector
{
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <param name="container"></param>
/// <returns></returns>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return !(item is Test value)
? null
: NumberTemplate;
}
/// <summary>
///
/// </summary>
public DataTemplate NumberTemplate { get; set; }
/// <summary>
///
/// </summary>
public DataTemplate DefaultTemplate { get; set; }
}
}
When using a ContentTemplate for the Number Property in the ContentControl the animation is working.
But when I use a ContentTemplateSelector the Animation is not triggered anymore.
What am I missing here?
Your data bindings are wrong, resulting in unhandled or unexpected data types.
Currently, your ContentControl.Content property (inside the DataTemplate for the type Test) binds to the Test.Number property of type int. Therefore, the object that is passed to the DataTemplateSelector is of type int and not Test. The condition in the DataTemplateSelector.SelectTemplate method will return false (as int is not Test is true) and make the DataTemplateSelector.SelectTemplate return null - no template. The XAML engine will call ToString on the int instance and is therefore able to display the value correctly (despite the lack of a DataTemplate).
Solution
You must fix the binding on the ContentControl.Content property to bind to the Test instance instead of binding to the Test.Number property:
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<ContentControl Width="50"
Margin="10"
Content="{Binding}"
ContentTemplateSelector="{StaticResource TemplateSelector}" />
</StackPanel>
</Border>
...
</DataTemplate>
Now that that the DataTemplateSelector can work properly, you must also adjust the NumberTemplate, so that it can display the Test.Number property properly. Since we have modified the binding source for the ContentControl.Content property, the new data type is now Test (instead of int):
<DataTemplate x:Key="NumberTemplate"
DataType="{x:Type Test}">
<TextBlock Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</DataTemplate>
Remarks
There is a lot of redundant code here. You will achieve the same results by dropping all the templates and the DataTemplateSelector by defining the ItemTemplate for the Test items properly.
The following drastically simplified version should also work:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Test}">
<Border x:Name="UpdateBorder"
Background="Aqua">
<StackPanel Orientation="Horizontal">
<TextBlock Width="50"
Margin="10"
Text="{Binding Text}" />
<TextBlock Width="50"
Margin="10"
Text="{Binding Number, NotifyOnTargetUpdated=True}" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard AutoReverse="True">
<ColorAnimation FillBehavior="Stop"
Storyboard.TargetName="UpdateBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#C5AFFFAA"
Duration="00:00:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Numbers}" />
</Grid>

Implementing color change for selection changed in ListBox

I'm working on Telephone App, which have a situation like when a missed call or unanswered call was recorded that phone number should appear red in listbox and when that number is selection changed it should come back to normal item's foreground color.
Xaml:
<ListBox x:Name="ListBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="370" ItemsSource="{Binding AllMissedCalls}" ItemContainerStyle="{StaticResource ListBoxItemStyle1}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="Hello"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I can implement it with VisualStates or I need to code?
Thanks,
Siva
At first I tried to use the VisualStateManger, but later I decided to make differently. I have created a dependency property where it was stored would highlight color and it could be used like this:
<ListBoxItem Name="Missed" local:DependencyPhoneClass.ColorOfState="{StaticResource MissedForegroundColor}">
Code of dependency property class:
public class DependencyPhoneClass : DependencyObject
{
public static DependencyProperty ColorOfStateProperty;
public static void SetColorOfState(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(ColorOfStateProperty, value);
}
public static Brush GetColorOfState(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(ColorOfStateProperty);
}
static DependencyPhoneClass()
{
ColorOfStateProperty = DependencyProperty.RegisterAttached("CollorOfState",
typeof(Brush),
typeof(DependencyPhoneClass),
new PropertyMetadata(OnColorStateNameChanged));
}
private static void OnColorStateNameChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var MyListBoxItem = sender as ListBoxItem;
if (MyListBoxItem == null)
{
throw new InvalidOperationException("This attached property only supports types derived from Control");
}
Brush ColorOfState = GetColorOfState(MyListBoxItem);
if (ColorOfState != null)
{
MyListBoxItem.Foreground = ColorOfState;
}
}
}
I created colors in resources:
<Window.Resources>
<SolidColorBrush x:Key="DefaultForegroundColor" Color="Black" />
<SolidColorBrush x:Key="MissedForegroundColor" Color="Red" />
<SolidColorBrush x:Key="UnansweredForegroundColor" Color="OrangeRed" />
</Window.Resources>
ListBox:
<ListBox Name="PhoneListBox" HorizontalAlignment="Center" VerticalAlignment="Top" Width="370" Height="100">
<ListBoxItem Name="Missed" local:DependencyPhoneClass.ColorOfState="{StaticResource MissedForegroundColor}" Content="Daddy: 1" />
<ListBoxItem Name="Unanswered" local:DependencyPhoneClass.ColorOfState="{StaticResource UnansweredForegroundColor}" Content="Mom: 15" />
<ListBoxItem Name="Normal" local:DependencyPhoneClass.ColorOfState="{StaticResource DefaultForegroundColor}" Content="Kim: 0" />
</ListBox>
Now, the color is set, it must be reset to default value when it is selected. This may be done in several ways:
Use the code:
private void ListBoxItem_Selected(object sender, RoutedEventArgs e)
{
ListBoxItem MyListBoxItem = sender as ListBoxItem;
Brush DefaultColor = this.Resources["DefaultForegroundColor"] as Brush;
DependencyPhoneClass.SetColorOfState(MyListBoxItem, DefaultColor);
}
Or use EventTrigger in XAML:
<ListBoxItem.Triggers>
<EventTrigger RoutedEvent="ListBoxItem.Selected">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Missed" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource DefaultForegroundColor}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBoxItem.Triggers>

Animate Content in a Grid Column

I'm trying to write a basic proof-of-concept for having a grid layout, with two columns with content in them, plus a third column that has only a GridSplitter. The content in the left-most column will be resized by the GridSplitter, but I'd also like to have some collapse and expand buttons that shrink/grow this same column. Basically like the Solution Explorer in visual studio, which can be dragged bigger, or just unpinned, which collapses it.
The code is below. LeftPanel is what I'm trying to animate. If I set a Width on LeftPanel, it animates, but no longer automatically re-sizes to fill the grid column when I drag the GridSplitter around. And when I leave off Width from LeftPanel, or set it to Auto, it animates, but no longer resizes with the GridSplitter.
I looked at this post about adding a DependencyProperty to animate the GridColumn directly, but again, dragging the GridSplitter would break it.
<UserControl.Resources>
<Storyboard x:Key="animShrink" x:Name="sbShrink">
<DoubleAnimation Storyboard.TargetName="leftPanel" Storyboard.TargetProperty="Width" To="50" Duration="0:0:2" />
</Storyboard>
<Storyboard x:Key="animGrow" x:Name="sbGrow">
<DoubleAnimation Storyboard.TargetName="leftPanel" Storyboard.TargetProperty="Width" To="200" Duration="0:0:2" />
</Storyboard>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftGridCol" Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Width="Auto" Margin="0,0,10,0" Background="Bisque" x:Name="leftPanel" >
<Button x:Name="btnShrink" Click="Button_Shrink" Height="20" VerticalAlignment="Top">Shrink</Button>
<Button x:Name="btnGrow" Click="Button_Grow" Height="20" Margin="0,30,0,0" VerticalAlignment="Top">Grow</Button>
</StackPanel>
<sdk:GridSplitter Grid.Column="0"></sdk:GridSplitter>
<StackPanel Grid.Column="2" Background="AliceBlue"></StackPanel>
</Grid>
Handlers
private void Button_Grow(object sender, RoutedEventArgs e) {
sbGrow.Begin();
}
private void Button_Shrink(object sender, RoutedEventArgs e) {
sbShrink.Begin();
}
Here's the best answer I could come up with. If anyone has a better one, then please let me know.
First, take the width off leftPanel, then bind to your own DependencyProperty, as explained in the link above:
public double LeftGridWidth {
get { return (double)GetValue(LeftGridWidthProperty); }
set { SetValue(LeftGridWidthProperty, value); }
}
public static readonly DependencyProperty LeftGridWidthProperty =
DependencyProperty.Register("LeftGridWidth", typeof(double), typeof(MainPage), new System.Windows.PropertyMetadata(new PropertyChangedCallback(ColumnWidthChanged)));
private static void ColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){
(d as MainPage).leftGridCol.Width = new GridLength((double)e.NewValue);
}
Then, in order to get the gridSplitter to not screw things up, manually adjust your dependency property as needed:
private void leftPanel_SizeChanged(object sender, SizeChangedEventArgs e) {
LeftGridWidth = leftGridCol.Width.Value;
}
(note, you have to set an initial width on the grid column for this to work. Setting the grid column to Auto will mess this up, since the width comes through as 1, messing up the initial layout.
You can use can you use ObjectAnimationUsingKeyFrames to directly animate width (GridLength) in column definitions like this:
<Storyboard x:Name="StoryboardLogin">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Width)" Storyboard.TargetName="LeftColumn">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
<DiscreteObjectKeyFrame KeyTime="0:0:0.20" Value="Auto"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftColumn" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
Not smooth unless you add more keyframes, but simple.

WPF Popup event handling - How to get triggered when Popup opens

I created a WPF Popup which contains a grid with border.
There is some animation associated with the border which I want to be triggered every time the Popup opens.
Currently the code is like this
<Popup x:Name="myPopUp" >
<Border x:Name="myBorder" >
<Border.Triggers>
<EventTrigger RoutedEvent="Popup.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myBorder"
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Grid />
</Border>
</Popup>
As per the code the border shows up the animation for the first time the popup opens.
What change do I need to make to trigger the border animation every time the Popup opens?
As per suggestions given here and a little bit expireince now (I asked this a year back :) ), I could figure out the solution.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<Style x:Key="popupStyle" TargetType="{x:Type Popup}" >
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Width="100" Height="100" Click="Button_Click"></Button>
<Popup Name="popUp" Width="100" Height="100" Style="{StaticResource popupStyle}" >
<Border x:Name="myBorder" Background="Blue"/>
</Popup>
</Grid>
and a sample code behind to trigger the popup..
private void Button_Click(object sender, RoutedEventArgs e)
{
popUp.PlacementTarget = (Button)sender;
popUp.IsOpen = true;
}
Although I can only animate the Popup and not the Border here, it pretty much gives the same result.
I'm not sure if the popup gets focus when it opens, but you could use the GotFocus event if it does. Alternatively, you could try using a datatrigger on the is IsOpen property. I think you'd have to put that in a style though instead of inline.
You can achieve this by listening to the IsOpen dependency property like
public MainWindow()
{
InitializeComponent();
//// Listening to the IsOpen dependency property of the Popup.
this.SetBinding(PopupIsOpenProperty, new Binding() { Source = this.popupContainer, Path = new PropertyPath("IsOpen") });
}
/// <summary>
/// Gets or sets a value indicating whether [popup is open].
/// </summary>
/// <value><c>true</c> if [popup is open]; otherwise, <c>false</c>.</value>
public bool PopupIsOpen
{
get { return (bool)GetValue(PopupIsOpenProperty); }
set { SetValue(PopupIsOpenProperty, value); }
}
// Using a DependencyProperty as the backing store for PopupIsOpen. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupIsOpenProperty =
DependencyProperty.Register("PopupIsOpen", typeof(bool), typeof(MainWindow), new PropertyMetadata(false,
(dependencyObject, e) =>
{
var mainWindow = (MainWindow)dependencyObject;
if (mainWindow != null &&
(bool)e.NewValue == true)
{
//// Raise your event here... like
//// mainWindow.RaisePopupOpened();
System.Diagnostics.Debug.WriteLine("Popup Open Triggered");
}
}));
private void button_MouseLeave(object sender, MouseEventArgs e)
{
this.popupContainer.IsOpen = false;
}
private void button_MouseMove(object sender, MouseEventArgs e)
{
//// Setting the popup position
var p = e.GetPosition(sender as UIElement);
this.popupContainer.HorizontalOffset = p.X;
this.popupContainer.VerticalOffset = p.Y;
//// Enabling popup when it is hover on the button
this.popupContainer.IsOpen = true;
}
<!-- XAML Starts here-->
<Grid>
<Button x:Name="button1" Content="This is a sample text" MouseMove="button_MouseMove" MouseLeave="button_MouseLeave" Width="100" Height="25" />
<Popup x:Name="popupContainer" IsHitTestVisible="False" >
<Grid Background="White">
<TextBlock Text="{Binding Content, ElementName=button}" />
</Grid>
</Popup>
</Grid>
HTH
In App.xaml.cs or in another starting class instance you need add:
var field = typeof(PresentationSource).GetField("RootSourceProperty", BindingFlags.NonPublic | BindingFlags.Static);
var property = (DependencyProperty)field.GetValue(null);
property.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(property.DefaultMetadata.DefaultValue, OnHwndSourceChanged));
Where, RootSourceProperty is private field DependecyProperty of PresentationSource. Its property use when HwndSource is created and set RootVisual. So you need just use property changed call back of RootSourceProperty:
private static void OnHwndSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
This is nice because, you can use it in your all Application and for all HwndSource (Popup, Window or Custom controls, where you are using HwndSource)
try changing your event trigger to
<EventTrigger RoutedEvent="Popup.Opened">

Best way to propagate VisualState changes

I am currently facing a scenario that I am unsure what is the best way to handle.
Scenario:
ControlA has 2 two custom visualstates, let’s call them “StateOn” and “StateOff”.
I now apply a template on ControlA, let’s call it “templateA”.
“templateA” has one control under it of type ControlB (who’s unaware of StateOn/Off).
ControlB has a Template that handles the visualstate changes of ControlA, namely, StateOn and StateOff.
Problem:
ControlB does not receive changes to the VisualStates fired on ControlA, thus no visual changes happen.
I think the problem has to do with the root element being a control (ControlB), which doesn’t fire gotostate on the desired states. However, I’m wondering what is the simplest/cleanest way to propagate ControlA’s visualstate changes to ControlB.
Thanks for your help!
Henry
Xaml:-
<UserControl x:Class="VisualStateChangePropagation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VisualStateChangePropagation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<SolidColorBrush x:Name="Fill_Bg_Red" Color="Red"/>
<SolidColorBrush x:Name="Fill_Bg_Blue" Color="Blue"/>
<ControlTemplate x:Name="templateA" TargetType="Control">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="StateOn">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="m_rect"
Storyboard.TargetProperty="(Rectangle.Fill)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Fill_Bg_Red}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="StateOff"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="m_rect" Fill="{StaticResource Fill_Bg_Blue}"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Name="templateB" TargetType="Control">
<local:ControlB Template="{StaticResource templateA}"/>
</ControlTemplate>
</Grid.Resources>
<local:ControlA x:Name="m_control1" Template="{StaticResource templateA}" Grid.Column="0"/>
<Button Click="Button_Click" Grid.Column="1" Content="swap"/>
<local:ControlA x:Name="m_control2" Template="{StaticResource templateB}" Grid.Column="2"/>
</Grid>
</UserControl>
code behind:
public class ControlA : Control
{
public void ToggleState()
{
m_isSet = !m_isSet;
UpdateVisualState();
}
private void UpdateVisualState()
{
string targetState = m_isSet ? "StateOn" : "StateOff";
VisualStateManager.GoToState(this, targetState, false);
}
private bool m_isSet = false;
}
public class ControlB : Control
{
}
First of all both ControlA and ControlB should have a Dependency Property for IsSet.
public bool IsSet
{
get { return (bool)GetValue(IsSetProperty); }
set { SetValue(IsSetProperty, value); }
}
public static readonly DependencyProperty IsSetProperty =
DependencyProperty.Register(
"IsSet",
typeof(bool),
typeof(ControlA), //Change in ControlB
new PropertyMetadata(false, OnIsSetPropertyChanged));
private static void OnIsSetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control source = d as Control;
string newState = (bool)e.NewValue ? "StateOn" : "StateOff";
VisualStateManager.GotoState(source, newState, true);
}
Contrary to your intuition visual states do not propagate at all. The states are only meaningfull to the control to which they are directly attached. However with this dependency property added to both controls you can now propagate the property value via template binding:-
<ControlTemplate x:Name="templateB" TargetType="Control">
<local:ControlB Template="{StaticResource templateA}" IsSet="{TemplateBinding IsSet}" />
</ControlTemplate>
As for you original ControlA code the m_isSet field is no longer required:-
public void ToggleState()
{
IsSet = !IsSet;
}

Resources