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.
Related
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>
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"));
}
}
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">
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;
}
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>