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

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

Related

WPF Attach VisualState to Object Property

I am working on expression Blend for VS2015, I have aListBox binded to an ObservableCollection of custom objects. Those objects expose Properties that arise the NotifyPropertyChanged, and everything works nice.
I can bind parts if the ItemTemplate to those Properties and my list work nice but what I want to do is to set the VisualState according to a certain bool (already configured or not). I also created some events (configured, confLost) and tried to target those events in the triggers panel but .. nothing worked.
How do I bind VisualStates to members of the bound object ??
ItemTemplate property works like any other DependencyProperty, it can be set/reset anytime and it's visual impact will be reflected on UI. see below example where I have bound a bool value to ToggleButton state and ItemControl's ItemTemplate is changed accordingly rendering different visual.
Update: I designed a Device class that has device name and it's state to make a similar situation. And another class MyVisualStateManager to create a bindable property. Cause VisualStateManager class doesn't expose any property to bind directly. code is as below:
XMAL
<Window x:Class="WpfStackOverflowTempProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
xmlns:local="clr-namespace:WpfStackOverflowTempProject"
>
<ItemsControl ItemsSource="{Binding list}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:UserControl1 DataContext="{Binding Name}" Width="200" BorderBrush="Black" BorderThickness="2" Padding="2">
<local:UserControl1.Style>
<Style TargetType="{x:Type local:UserControl1}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="0">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State1" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="1">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State2" />
</DataTrigger>
</Style.Triggers>
</Style>
</local:UserControl1.Style>
</local:UserControl1>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
UserControl:
<UserControl x:Class="WpfStackOverflowTempProject.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="State1">
<Storyboard>
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
<VisualState x:Name="State2">
<Storyboard>
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Name="State2Panel" Background="Green" Opacity="0"/>
<Border Name="State1Panel" Background="Red" Opacity="1"/>
<TextBlock Text="{Binding Path=.}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
list = new List<Device>();
list.Add(new Device() {Name="Device 1",DeviceState = 0 });
list.Add(new Device() { Name = "Device 2", DeviceState = 1 });
list.Add(new Device() { Name = "Device 3", DeviceState = 0 });
list.Add(new Device() { Name = "Device 4", DeviceState = 2 });
list.Add(new Device() { Name = "Device 5", DeviceState = 1 });
InitializeComponent();
}
public List<Device> list { get; set; }
}
public class Device : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
updateProperty("Name");
}
}
private int deviceState;
public int DeviceState
{
get { return deviceState; }
set
{
deviceState = value;
updateProperty("DeviceState");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void updateProperty(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Helper Class: This class exposes an attached property VisualState that could be bound to any value in xaml.
public class MyVisualStateManager
{
public static string GetVisualState(DependencyObject obj)
{
return (string)obj.GetValue(VisualStateProperty);
}
public static void SetVisualState(DependencyObject obj, string value)
{
obj.SetValue(VisualStateProperty, value);
}
// Using a DependencyProperty as the backing store for VisualState. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VisualStateProperty =
DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(MyVisualStateManager), new PropertyMetadata(new PropertyChangedCallback(VisualStateChanged)));
public static void VisualStateChanged(DependencyObject Do, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
string state = e.NewValue.ToString();
var control = Do as FrameworkElement;
VisualStateManager.GoToState(control, state, true);
}
}
}
Output
Different Item representing different devices and visual is changed on basis of theirDevicestateproperty which causes aTriggerto get executed inUserControl1.
Although Kylo's solution would provably work, the people at Microsoft already worked a code-free, 3-clicks-away solution for such a simple action to do.
Solution is on Behaviors, there is one behavior called GoToStateAction you have to add one of them to your control and there you can set your trigger (that can be set as DataTrigger). In my case I binded to a property of the type enum.
Then you can set the Comparison and the value (equals to "ReadyToUse")
Then as an outcome of the comparison you can trigger a state change for a particular object, you set your object, and you select the state from a nice combobox. There is even a checbox for using your transitions.
I mean in the user interface of Blend, where to click to get the conditional working,
In Blend look for the Objects and Timeline panel.
Select the control in your defined ItemTemplate (you have set one up right?) which you want to have the state changes. In Kylo's example above we would have selected the TextBox.
Right click and select Edit Style the either create a new style (most likely) or Edit a Copy which works of any existing inherited styles.
From there one can work on the properties tab to change specifics. Most likely you will be working directly in the xaml to do specific operations.
Even though these docs for Version 2, they still apply, if nothing else can give you an overview of Blend
Create a style resource
Style and template overview

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>

Slider "animation" in 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"));
}
}

WPF: How can I avoid the flickering of the checked checkboxes in a ListBox or a ListView?

How can I avoid the flickering of the checked checkboxes in a WPF ListBox or ListView ? It can be reproduced with the code below by clicking on the Refresh button or by scrolling the listbox. If IsChecked is false, it does not flicker.
Window1.xaml:
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Name="listBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True"
VerticalAlignment="Center"/>
<Label Padding="3"
Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Refresh"
Grid.Column="1"
VerticalAlignment="Top"
Click="Button_Click"/>
</Grid>
</Window>
Window1.xaml.cs:
using System.Windows;
namespace WpfApplication6
{
partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Button_Click(null, null);
}
void Button_Click(object sender, RoutedEventArgs e)
{
var items = new int[10000];
for (int i = 0; i < items.Length; i++)
items[i] = i + 1;
listBox.ItemsSource = items;
}
}
}
It is flickering because you are throwing out the old ItemsSource and creating a new one. This requires all of the binding to be redone, and the template displaying each item needs to be recreated. To avoid the performance overhead of recreating an entire list, just modify the individual elements in the existing ItemsSource. Then the part of the DataTemplate that is bound to the changed properties and/or items will automatically update without needing to recreate the whole list view. Doing this will eliminate the "flicker" you are seeing.
Try this for the codebehind:
public partial class MainWindow : Window
{
private ObservableCollection<object> _items;
public MainWindow()
{
InitializeComponent();
_items = new ObservableCollection<object>();
for (int i = 0; i < 10000; i++)
_items.Add(i + 1);
listBox.ItemsSource = _items;
}
void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < _items.Count;i++)
{
if (!(_items[i] is int)) continue;
_items[i] = (int)_items[i] + 1;
}
}
}
Do you mean checkbox being checked? I think you need to change the animation when you check / set the checkbox checked.
It does not occur on Windows XP (that's why I think it's an animation), I haven't tested Vista :)
Good luck.
+1 to Snake who has the right answer here.
To add to this:
The CheckBox control has a control template with storyboard animation which animates the checked icon on/off when the checked state changes. The Checked state changes as you are binding to ObservableCollection and recreating the ItemsSource which is causing new Checkboxes to be created (with IsChecked=false) and bound to your ViewModel (which probably results in IsChecked=True).
To disable this 'feature' you can either modify how you fill an ObservableCollection, or if that's not possible, you can change the template / style of the Checkbox.
Just reverse engineer the ControlTemplate of Checkbox (using blend, or by using one of the WPF Themes) and find these lines. you need to set the duration of the two animations to zero
<!-- Inside the CheckBoxTemplate ControlTemplate -->
<Storyboard x:Key="CheckedTrue">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CheckIcon"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CheckedFalse">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="CheckIcon"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00.4000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>

How to get View element to fade in/out based on value of ViewModel property?

The View and ViewModel listed below show two buttons:
when you click Show ToolBar, the toolbar fades in
when you click Hide ToolBar, the toolbar fades out
However, the following things don't work:
when the application is loaded and OnPropertyChanged("PageToolBarVisible") is fired, if the value is false then the Toolbar shows in spite of that fact (why is that?)
when the application is loaded and OnPropertyChanged("PageToolBarVisible") is fired, if the value is true then the toolbar does indeed fade in, however, this is not supposed to happen on loading but only when explicitly changed by pressing the buttons, so I change the constructor to do this: _pageToolBarVisible = "true", but then the toolbar still still fades in even though the OnPropertyChanged was never called (why is that?)
View:
<Window x:Class="TestAnim334.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:TestAnim334.Commands"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<Style x:Key="PageToolBarStyle" TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding PageToolBarVisible}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.0"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<Trigger Property="Opacity" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Top"
Margin="10">
<TextBlock Text="This is the content of the page."/>
<TextBlock Text="The ViewModel property is:"/>
<TextBlock Text="{Binding PageToolBarVisible}"/>
<Button Content="Hide ToolBar"
Width="150"
Command="{Binding HideToolBarCommand}"
HorizontalAlignment="Left"/>
<Button Content="Show ToolBar"
Width="150"
Command="{Binding ShowToolBarCommand}"
HorizontalAlignment="Left"/>
</StackPanel>
<Border Style="{StaticResource PageToolBarStyle}"
Height="40"
DockPanel.Dock="Bottom" Background="#ddd" CornerRadius="5">
<TextBlock FontSize="24" Text="This is the ToolBar text"/>
</Border>
</DockPanel>
</Window>
ViewModel:
using System.Windows.Input;
using TestAnim334.Commands;
namespace TestAnim334.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region ViewModelProperty: PageToolBarVisible
private string _pageToolBarVisible;
public string PageToolBarVisible
{
get
{
return _pageToolBarVisible;
}
set
{
_pageToolBarVisible = value;
OnPropertyChanged("PageToolBarVisible");
}
}
#endregion
#region DelegateCommand: HideToolBar
private DelegateCommand hideToolBarCommand;
public ICommand HideToolBarCommand
{
get
{
if (hideToolBarCommand == null)
{
hideToolBarCommand = new DelegateCommand(HideToolBar, CanHideToolBar);
}
return hideToolBarCommand;
}
}
private void HideToolBar()
{
PageToolBarVisible = "false";
}
private bool CanHideToolBar()
{
return PageToolBarVisible == "true";
}
#endregion
#region DelegateCommand: ShowToolBar
private DelegateCommand showToolBarCommand;
public ICommand ShowToolBarCommand
{
get
{
if (showToolBarCommand == null)
{
showToolBarCommand = new DelegateCommand(ShowToolBar, CanShowToolBar);
}
return showToolBarCommand;
}
}
private void ShowToolBar()
{
PageToolBarVisible = "true";
}
private bool CanShowToolBar()
{
return PageToolBarVisible == "false";
}
#endregion
public MainViewModel()
{
PageToolBarVisible = "false";
}
}
}
Ok to answer the two parts of your question:
Why when PageToolBarVisible is fired as "False" at loading the toolbar still shows:
Your only hiding the toolbar with the animation in the "ExitActions", which aren't being hit. The logic flows as such.
if(PageToolBarVisible == true)
Run EnterActions
if(PageToolBarVisible changes to false)
Run ExitActions
if(PageToolBarVisible starts as false)
Do nothing
if(PageToolBarVisible starts as false and is set to false)
Do nothing
Conclusion, because the PageToolBarVisible is not changing from True to False ... the animation doesn't run.
Solution:
Consider having a second DataTrigger that handles the False case for your PageToolBarVisible property. Or you can set the Property to True and then False to hit your ExitActions (though I'm not sure if this would A) work or B) be a good solution)
Why setting the backing field for the Property still runs the animation when loaded:
I believe what is happening here, is that when the application loads, the Binding is checking the value of the property, if you've set the value of the backing field then it should be getting this from the "Get" on "PageToolBarVisible".
So it's not that you're triggering the OnPropertyChanged, it's that the Binding is getting the value when your app is loading
Solution:
Either rethink your logic around how your binding to the trigger and thus the animation. Or you can play with the Binding Modes, to be honest I don't think there's a mode that would satisfy the conditions you're looking for, however I might be wrong.

Resources