How do I reset my WPF Animation - c#

So I created a UserControl, and I created a dependency property for it so I can use it for my animation.
The animation works, it sets off but when I click a new item it doesnt slide back in it jumps back to the start and then restarts.
How do I make the animation slide back in.
This is due to the true and false statements in the OnSelectionChanged.
<Grid.Resources>
<system:Double x:Key="SlideOffSet">50</system:Double>
<Storyboard x:Key="SlideRight">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
From="0" To="{StaticResource SlideOffSet}"
Duration="0:0:0.2" />
</Storyboard>
<Storyboard x:Key="SlideLeft">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
From="{StaticResource SlideOffSet}" To="0"
Duration="0:0:0.2" />
</Storyboard>
</Grid.Resources>
<local:CustomizedList x:Name="uc" Margin="5" Grid.Column="0" DataContext="{Binding}" />
<StackPanel Grid.Column="2"
Width="50"
Height="50"
Background="Gray">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=uc, Path=SelectedItem}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SlideRight}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource SlideLeft}" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel.RenderTransform>
<TranslateTransform />
</StackPanel.RenderTransform>
</StackPanel>
UserControl
<ListBox ItemsSource="{Binding}" x:Name="list" HorizontalContentAlignment="Stretch" SelectionChanged="List_OnSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="2" BorderThickness="2" CornerRadius="3" BorderBrush="Brown">
<TextBlock Text="{Binding}" Margin="1">
<TextBlock.Background>
<LinearGradientBrush>
<GradientStop Offset="0" Color="AliceBlue"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</TextBlock.Background>
</TextBlock>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the SelectionChanged event for the Dependency Property
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(bool), typeof(CustomizedList));
public bool SelectedItem
{
get { return (bool)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void List_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = false;
SelectedItem = true;
}

When the item is selected I want it to be true and when another item is selected, make it slide back and slide out again
Well, then you need to wait asynchronously for the animation to occur before you set the property back to true. Try this:
private async void List_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = false;
await Task.Delay(1000); //wait for a second
SelectedItem = true;
}

Related

How to show/hide Rectangles on TabItem selection depending on a Tab Control ViewModel in WPF?

I have a WPF application where I need to use Tab Control. In my Tab Control I have total three tab items.
Here my MainWindow.xaml code :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
</Grid>
Due to my design purpose I need to underline the TabItem whenever it is selected. To give more design to the UI, I add some Data trigger animation into it. My approach is, I give two rectangles under the first and second Tab item and bind With Tab Control selected index. So, every time any individual Tab item is selected the Enter animation and Exit animation will fired/execute. I do not give any rectangle under the third tab item because the previous two rectangles will cover tho whole animation of those three tab items and it's sufficient.
But the problem is, the Rectangles Enter animations and Exit animations are interfere with each other and both are execute at a same time when I click any specific tab item bounded to the tab control selected index.
To overcome this issue I implement a View Model. Using this View Model I can track the last two tab items which I selected. To verify that my View Model is perfectly working or not. I tested with a Button. If I click the button then a Message Box will appear and it will show my current and Previous tab item which I selected.
But I don't know How to hide and show Rectangles depending on the last two tab items which I selected through the View Model. My approach is only to show the rectangles that are bounded to their Tab items. I give an example suppose the first Tab item named "Section 1" has a Rectangle named Rect1 and the second Tab item named "Section 2" has a Rectangle named Rect2. this Rect1 and Rect2 are only visible if their parent tab item is selected through under the View Model.
Here is my Complete MainWindow.xaml code :
<Window.Resources>
<local:TestViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl DataContext="{StaticResource MainViewModel}"
SelectedIndex="{Binding Selected}"
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
<Button Content="Check
Selected Index"
Grid.Row="1"
x:Name="TestButton"
Click="TestButton_OnClick"/>
<DockPanel x:Name="rp" Grid.Row="0" LastChildFill="False" HorizontalAlignment="Stretch">
<Canvas DockPanel.Dock="Left" >
<Rectangle x:Name="Rect1" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="6,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="1">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard3"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard4"/>
<BeginStoryboard Name="MyBeginStoryboard1">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard2">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard1"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard2"/>
<BeginStoryboard Name="MyBeginStoryboard3">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="123.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard4">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="123.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle x:Name="Rect2" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="68,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<BeginStoryboard Name="MyBeginStoryboard5">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard6">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Canvas>
</DockPanel>
</Grid>
Here is my Main View Model code name as TestViewModel :
class TestViewModel : INotifyPropertyChanged
{
private int _selected;
public int Selected
{
get { return _selected; }
set
{
int temp = _selected;
_selected = value;
_previousSelected = temp;
NotifyPropertyChanged("Selected", temp, value);
NotifyPropertyChanged("PreviousSelected", temp, temp);
}
}
int _previousSelected = 0;
public int PreviousSelected
{
get { return _previousSelected; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
}
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
Here is the MainWindow.xaml.cs logic code where I tested the View model with a Button and Message Box to see if the last two item selection is perfectly working or not.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TestButton_OnClick(object sender, RoutedEventArgs e)
{
var vm = TestTabs.DataContext as TestViewModel;
MessageBox.Show(string.Format("You selected tab {0} ,previous selected tab{1}", vm.Selected, vm.PreviousSelected));
}
}
I don't know that my approach is correct or not that means hiding or showing the rectangles depending on that View Model is a good idea or not but I need some similar solution and it's may be different that means the rectangles are only trigger their animation depending on View model. But the answer is almost same. All type of suggestion are accepted.
I attach a GIF so that people understand that this effect I want to get in my TabControl on Tab Item selection. I know it can be done easily with code behind but I want to get solution in pure XAML and MVVM base.

Animation on Listview with Observablecollection, XamlParseException occured when sort items in WPF

I'm suffering from cannot found property Background.Opacity when I use Animation on Listview.
See my Gifs. following images shows my current work state.
I got an Error message that says - cannot found property Backgorund.Opactiy. I think It's weird, Because It works until I Sort it.
see my xaml source first.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding bsources}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="True">
<Setter Property="Background">
<Setter.Value>
<DrawingBrush
x:Name="errorBrush"
TileMode="Tile"
Viewport="0 0 1 1"
ViewportUnits="Absolute">
<DrawingBrush.RelativeTransform>
<RotateTransform Angle="45" />
</DrawingBrush.RelativeTransform>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,0" EndPoint="4,2" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="Red" Thickness="4" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Setter.Value>
</Setter>
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!-- Storyboard.TargetProperty = "opacity" throws no error -->
<DoubleAnimationUsingKeyFrames
FillBehavior="Stop"
Storyboard.TargetProperty="Background.Opacity"
Duration="0:0:1">
<LinearDoubleKeyFrame KeyTime="0:0:0.0" Value="0.2" />
<LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="0.3" />
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="0.8" />
<LinearDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox IsChecked="{Binding Checked}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Name}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
Name="Sort"
Grid.Row="2"
Click="Sort_Click"
Content="Sort" />
</Grid>
List view has Item sources. and If item's Property Checked is true, then It begins storyboard and also makes Its background to red drawingbrush. the reason I think this error is weird that It works good when I click an item and make Checked Property is true. but when I sort ObservableCollection and allocate new ObservableCollection It throws an error. ...
in xaml Code, see my comment. at Lines, there, I tried without Background prefix. Storyboard.TargetProperty="Opacity" then It works. see follow image.
You may notify differences between first image and second image, but let me explain differences to you. first one only changes its background brush opacity, but second one change all content (including text) opacity. I want achieve first one animation without any error.
Thank you for reading.
I don't think I have a problem with csharp code - But I'll post my source code also. following source is my csharp source.
public partial class MainWindow : Window, INotifyPropertyChanged
{
bool state = false;
private ObservableCollection<Person> _bSources;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChagned([CallerMemberName] string name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public ObservableCollection<Person> bsources
{
get { return _bSources; }
set { _bSources = value; OnPropertyChagned(); }
}
public MainWindow()
{
bsources = new ObservableCollection<Person>
{ new Person("김", "부산"), new Person("정", "강원도"), new Person("최", "서울"), new Person("권", "전라도"), new Person("박", "대전") };
InitializeComponent();
DataContext = this;
}
private void Sort_Click(object sender, RoutedEventArgs e)
{
if (state)
{
bsources = new ObservableCollection<Person>(bsources.OrderBy(i => i.Checked));
state = false;
}
else
{
bsources = new ObservableCollection<Person>(bsources.OrderByDescending(i => i.Checked));
state = true;
}
}
}
public class Person : INotifyPropertyChanged
{
private bool _Checked;
public bool Checked
{
get { return _Checked; }
set { _Checked = value; OnPropertyChagned(); }
}
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChagned(); }
}
private string _Address;
public string Address
{
get { return _Address; }
set { _Address = value; OnPropertyChagned(); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChagned([CallerMemberName] string name=null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public Person(string name, string Address)
{
this.Checked = false;
this.Name = name;
this.Address = Address;
}
}
EDITED
Button ADDED for update HasError property that is just checking whether checkbox is checked or not.
following is my xaml code, little change from original xaml source. Now Binding property of DataTrigger is HasError and DataTrigger.EnterAction is Changed to DataTrigger.ExitAction. and below that stack panel I added new button to update HasError of Person class Property.
<ListView ItemsSource="{Binding bsources}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasError}" Value="True">
<Setter Property="Background">
<Setter.Value>
<DrawingBrush
x:Name="errorBrush"
TileMode="Tile"
Viewport="0 0 1 1"
ViewportUnits="Absolute">
<DrawingBrush.RelativeTransform>
<RotateTransform Angle="45" />
</DrawingBrush.RelativeTransform>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,0" EndPoint="4,2" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="Red" Thickness="4" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Setter.Value>
</Setter>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
FillBehavior="Stop"
Storyboard.TargetProperty="Background.Opacity"
Duration="0:0:1">
<LinearDoubleKeyFrame KeyTime="0:0:0.0" Value="0.2" />
<LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="0.3" />
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="0.8" />
<LinearDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox IsChecked="{Binding Checked}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Name}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
Name="UpdateAnimation"
Grid.Row="1"
Click="UpdateAnimation_Click"
Content="Update Animation" />
//in class Mainwindow
private void UpdateAnimation_Click(object sender, RoutedEventArgs e)
{
// update HasError
foreach( Person p in bsources)
p.HasError = p.Checked;
}
//in Person Class
/// <summary>
/// added
/// </summary>
private bool _HasError;
public bool HasError
{
get { return _HasError; }
set { _HasError = value; OnPropertyChagned(); }
}
following gif shows an error message.
the last exception, you might ignore it. I solved with added following xaml source.
<DataTrigger Binding="{Binding HasError}" Value="false">
<Setter Property="Background">
<Setter.Value>
<DrawingBrush Opacity="0" />
</Setter.Value>
</Setter>
</DataTrigger>
but as you see, in Gif, there is no opacity incremental changes. ... sob ..
To short, there is no incremental opacity changes with DataTrigger.ExitActions.
I believe the problem is with the Stackpanel not yet rendered (with it's DrawingBrush background which is animated) when you assign new instance of ObservableCollection. The DataTrigger EnterAction is triggered first, even before the StackPanel background is rendered. On this assumption, I managed to make your sorting work only by changing
DataTrigger.EnterActions to DataTrigger.ExitActions. I also need to add a fallback default DrawingBrush in case you remove the check. Below the updated style:
<Style TargetType="{x:Type StackPanel}">
<Style.Setters>
<Setter Property="Background">
<Setter.Value>
<DrawingBrush />
<!--A default drawing brush-->
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}"
Value="True">
<Setter Property="Background">
<Setter.Value>
<DrawingBrush x:Name="errorBrush"
TileMode="Tile"
Viewport="0 0 1 1"
ViewportUnits="Absolute">
<DrawingBrush.RelativeTransform>
<RotateTransform Angle="45" />
</DrawingBrush.RelativeTransform>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,0"
EndPoint="4,2" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="Red"
Thickness="4" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Setter.Value>
</Setter>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<!-- Storyboard.TargetProperty = "opacity" throws no error -->
<DoubleAnimationUsingKeyFrames FillBehavior="Stop"
Storyboard.TargetProperty="Background.Opacity"
Duration="0:0:1">
<LinearDoubleKeyFrame KeyTime="0:0:0.0"
Value="0.2" />
<LinearDoubleKeyFrame KeyTime="0:0:0.3"
Value="0.3" />
<LinearDoubleKeyFrame KeyTime="0:0:0.5"
Value="0.8" />
<LinearDoubleKeyFrame KeyTime="0:0:1"
Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
UPDATED ANSWER
This is how I managed to get your updated question with HasError to work. Please give this a try:
<ListView ItemsSource="{Binding bsources}">
<ListView.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<DrawingBrush x:Key="DB" Opacity="0" TileMode="Tile" Viewport="0 0 1 1" ViewportUnits="Absolute">
<DrawingBrush.RelativeTransform>
<RotateTransform Angle="45" />
</DrawingBrush.RelativeTransform>
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,0" EndPoint="4,2" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="Red" Thickness="4" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Setters>
<Setter Property="Background" Value="{StaticResource DB}"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding HasError}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
FillBehavior="HoldEnd"
Storyboard.TargetProperty="Background.Opacity"
Duration="0:0:1">
<LinearDoubleKeyFrame KeyTime="0:0:0.0" Value="0.2" />
<LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="0.3" />
<LinearDoubleKeyFrame KeyTime="0:0:0.5" Value="0.8" />
<LinearDoubleKeyFrame KeyTime="0:0:1" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding HasError}" Value="false">
<Setter Property="Background">
<Setter.Value>
<DrawingBrush Opacity="0" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<CheckBox IsChecked="{Binding Checked}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Name}" />
<TextBlock Margin="20,0,0,0" Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

TemplateBinding in ControlTemplate in Style of CustomControl [duplicate]

I'm trying to create a button that behaves similarly to the "slide" button on the iPhone. I have an animation that adjusts the position and width of the button, but I want these values to be based on the text used in the control. Currently, they're hardcoded.
Here's my working XAML, so far:
<CheckBox x:Class="Smt.Controls.SlideCheckBox"
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"
xmlns:local="clr-namespace:Smt.Controls"
xmlns:System.Windows="clr-namespace:System.Windows;assembly=PresentationCore"
Name="SliderCheckBox"
mc:Ignorable="d">
<CheckBox.Resources>
<System.Windows:Duration x:Key="AnimationTime">0:0:0.2</System.Windows:Duration>
<Storyboard x:Key="OnChecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="40" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="41" />
</Storyboard>
<Storyboard x:Key="OnUnchecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="0" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="40" />
</Storyboard>
<Style x:Key="SlideCheckBoxStyle"
TargetType="{x:Type local:SlideCheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SlideCheckBox}">
<Canvas>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Canvas>
<!--Background-->
<Rectangle Width="{Binding ElementName=ButtonText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Fill="LightBlue" />
</Canvas>
<Canvas>
<!--Button-->
<Button Width="{Binding ElementName=CheckedText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Name="CheckButton"
Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}">
<Button.RenderTransform>
<TransformGroup>
<TranslateTransform />
</TransformGroup>
</Button.RenderTransform>
</Button>
</Canvas>
<Canvas>
<!--Text-->
<StackPanel Name="ButtonText"
Orientation="Horizontal"
IsHitTestVisible="False">
<Grid Name="CheckedText">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=CheckedText}" />
</Grid>
<Grid Name="UncheckedText"
HorizontalAlignment="Right">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=UncheckedText}" />
</Grid>
</StackPanel>
</Canvas>
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource OnChecking}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource OnUnchecking}" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Resources>
<CheckBox.CommandBindings>
<CommandBinding Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}"
Executed="OnSlideCheckBoxClicked" />
</CheckBox.CommandBindings>
</CheckBox>
And the code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Smt.Controls
{
public partial class SlideCheckBox : CheckBox
{
public SlideCheckBox()
{
InitializeComponent();
Loaded += OnLoaded;
}
public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text"));
public string CheckedText
{
get { return (string)GetValue(CheckedTextProperty); }
set { SetValue(CheckedTextProperty, value); }
}
public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text"));
public string UncheckedText
{
get { return (string)GetValue(UncheckedTextProperty); }
set { SetValue(UncheckedTextProperty, value); }
}
public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand();
void OnLoaded(object sender, RoutedEventArgs e)
{
Style style = TryFindResource("SlideCheckBoxStyle") as Style;
if (!ReferenceEquals(style, null))
{
Style = style;
}
}
void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e)
{
IsChecked = !IsChecked;
}
}
}
The problem comes when I try to bind the "To" attribute in the DoubleAnimations to the actual width of the text, the same as I'm doing in the ControlTemplate. If I bind the values to an ActualWidth of an element in the ControlTemplate, the control comes up as a blank checkbox (my base class). However, I'm binding to the same ActualWidths in the ControlTemplate itself without any problems. Just seems to be the CheckBox.Resources that have a problem with it.
For instance, the following will break it:
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="{Binding ElementName=CheckedText, Path=ActualWidth}" />
I don't know whether this is because it's trying to bind to a value that doesn't exist until a render pass is done, or if it's something else. Anyone have any experience with this sort of animation binding?
I've had similar situations in ControlTemplates where I've wanted to bind the "To" attribute to a value (rather than hard-coding it), and I finally found a solution.
Quick side note: If you dig around on the web you'll find examples of people being able to use data binding for the "From" or "To" properties. However, in those examples the Storyboards are not in a Style or ControlTemplate. If your Storyboard is in a Style or ControlTemplate, you'll have to use a different approach, such as this solution.
This solution gets around the freezable issue because it simply animates a double value from 0 to 1. It works with a clever use of the Tag property and a Multiply converter. You use a multibinding to bind to both a desired property and your "scale" (the Tag), which get multiplied together. Basically the idea is that your Tag value is what you animate, and its value acts like a "scale" (from 0 to 1) bringing your desired attribute value to "full scale" once you've animated the Tag to 1.
You can see this in action here. The crux of it is this:
<local:MultiplyConverter x:Key="multiplyConverter" />
<ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
<!-- (other stuff here...) -->
<ScrollViewer x:Name="ExpanderContentScrollView">
<!-- ** BEGIN IMPORTANT PART #1 ... -->
<ScrollViewer.Tag>
<sys:Double>0.0</sys:Double>
</ScrollViewer.Tag>
<ScrollViewer.Height>
<MultiBinding Converter="{StaticResource multiplyConverter}">
<Binding Path="ActualHeight" ElementName="ExpanderContent"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</ScrollViewer.Height>
<!-- ...end important part #1. -->
<ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
</ScrollViewer>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!-- ** BEGIN IMPORTANT PART #2 (make TargetProperty 'Tag') ... -->
<DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
Storyboard.TargetProperty="Tag"
To="1"
Duration="0:0:0.4"/>
<!-- ...end important part #2 -->
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
With this value converter:
public class MultiplyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 1.0;
for (int i = 0; i < values.Length; i++)
{
if (values[i] is double)
result *= (double)values[i];
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("Not implemented");
}
}
As far as I know, you can't bind the animation to/from because the animation has to be freezable.
I like #Jason Frank's solution. However it is even easier and less error-prone if you don't use the Tag, but instead e.g. the Width property of an empty dummy Border element. It is a native double property, so no need for <sys:Double> syntax and you can name the Border just like you would do with a variable like so:
<!-- THIS IS JUST USED FOR SLIDING ANIMATION MATH -->
<!-- animated Border.Width From 0 to 1 -->
<Border x:Name="Var_Animation_0to1" Width="0"/>
<!-- animated Border.Width From 0 to (TotalWidth-SliderWidth) -->
<Border x:Name="Var_Slide_Length">
<Border.Width>
<MultiBinding Converter="{mvvm:MathConverter}" ConverterParameter="a * (b-c)">
<Binding ElementName="Var_Animation_0to1" Path="Width"/>
<Binding ElementName="BackBorder" Path="ActualWidth"/>
<Binding ElementName="Slider" Path="ActualWidth"/>
</MultiBinding>
</Border.Width>
</Border>
That makes the bindings much more readable.
The Animation is always 0..1, as Jason pointed out:
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetProperty="Width" Storyboard.TargetName="Var_Animation_0to1">
<DoubleAnimation To="1" Duration="00:00:00.2"/>
</Storyboard>
</BeginStoryboard>
Then bind whatever you want to animate to the Width of the dummy border. This way you can even chain converters to each other like so:
<Border x:Name="Slider" HorizontalAlignment="Left"
Margin="{Binding ElementName=Var_Slide_Length, Path=Width, Converter={StaticResource DoubleToThickness}, ConverterParameter=x 0 0 0}"/>
Combined with the MathConverter you can do almost anything in styles:
https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML
I implemented this exact thing.
<UserControl x:Class="YOURNAMESPACE.UserControls.SliderControl"
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"
xmlns:local="clr-namespace:YOURNAMESPACE.UserControls"
xmlns:converter="clr-namespace:YOURNAMESPACE.ValueConverters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
SizeChanged="UserControl_SizeChanged">
<UserControl.Resources>
<converter:MathConverter x:Key="mathConverter" />
<LinearGradientBrush x:Key="CheckedBlue" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#e4f5fc" Offset="0" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#bfe8f9" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="CheckedOrange" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFCA6A13" Offset="0" />
<GradientStop Color="#FFF67D0C" Offset="0.1" />
<GradientStop Color="#FFFE7F0C" Offset="0.1" />
<GradientStop Color="#FFFA8E12" Offset="0.5" />
<GradientStop Color="#FFFF981D" Offset="0.5" />
<GradientStop Color="#FFFCBC5A" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="CheckedOrangeBorder" Color="#FF8E4A1B" />
<SolidColorBrush x:Key="CheckedBlueBorder" Color="#FF143874" />
<Style x:Key="CheckBoxSlider" TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}" >
<DockPanel x:Name="dockPanel"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<DockPanel.Resources>
<Storyboard x:Key="ShowRightStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="ShowLeftStoryboard" >
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="slider"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"
>
<SplineDoubleKeyFrame x:Name="RightHalfKeyFrame" KeyTime="00:00:00.1000000" Value="300" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</DockPanel.Resources>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center" />
<Grid>
<Border x:Name="BackgroundBorder" BorderBrush="#FF939393" BorderThickness="1" CornerRadius="3"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFB5B5B5" Offset="0" />
<GradientStop Color="#FFDEDEDE" Offset="0.1" />
<GradientStop Color="#FFEEEEEE" Offset="0.5" />
<GradientStop Color="#FFFAFAFA" Offset="0.5" />
<GradientStop Color="#FFFEFEFE" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="LeftTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=LeftText, Mode=TwoWay}" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock x:Name="RightTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=RightText, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Border>
<Border x:Name="slider"
BorderBrush="#FF939393"
HorizontalAlignment="Left"
Width="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}"
Height="{TemplateBinding Height}"
BorderThickness="1"
CornerRadius="3"
RenderTransformOrigin="0.5,0.5" Margin="0"
>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<SkewTransform AngleX="0" AngleY="0" />
<RotateTransform Angle="0" />
<TranslateTransform X="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}" Y="0" />
</TransformGroup>
</Border.RenderTransform>
<Border.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0" />
<GradientStop Color="#FFCDCDCD" Offset="0.1" />
<GradientStop Color="#FFFBFBFB" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<DockPanel Background="Transparent" LastChildFill="False">
<Viewbox x:Name="SlideRight" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Right" Margin="0,0,50,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
<Viewbox x:Name="SlideLeft" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Left" Margin="50,0,0,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Path.LayoutTransform>
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
</DockPanel>
</Border>
</Grid>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedOrange}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedOrangeBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedBlue}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedBlueBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Visible" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBox"
Style="{StaticResource CheckBoxSlider}"
HorizontalAlignment="Stretch"
DockPanel.Dock="Top"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=Height, Mode=TwoWay}"
IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=IsLeftVisible, Mode=TwoWay}"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"
/>
</Grid>
</UserControl>
code behind.
namespace YOURNAMESPACE.UserControls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
/// <summary>
/// Interaction logic for SliderControl.xaml
/// </summary>
public partial class SliderControl : UserControl
{
public static readonly DependencyProperty IsLeftVisibleProperty =
DependencyProperty.RegisterAttached(
"IsLeftVisible",
typeof(bool),
typeof(SliderControl),
new UIPropertyMetadata(true, IsLeftVisibleChanged));
public static readonly DependencyProperty LeftTextProperty =
DependencyProperty.RegisterAttached(
"LeftText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, LeftTextChanged));
public static readonly DependencyProperty RightTextProperty =
DependencyProperty.RegisterAttached(
"RightText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, RightTextChanged));
/// <summary>
/// Initializes a new instance of the <see cref="SliderControl"/> class.
/// </summary>
public SliderControl()
{
this.InitializeComponent();
}
public string LeftText { get; set; }
public string RightText { get; set; }
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static bool GetIsLeftVisible(SliderControl sliderControl)
{
return (bool)sliderControl.GetValue(IsLeftVisibleProperty);
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetLeftText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(LeftTextProperty);
}
public static void SetIsLeftVisible(SliderControl sliderControl, bool value)
{
sliderControl.SetValue(IsLeftVisibleProperty, value);
}
public static void IsLeftVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
if ((bool)e.NewValue == true)
{
slider.RunAnimation("ShowLeftStoryboard");
}
else
{
slider.RunAnimation("ShowRightStoryboard");
}
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetRightText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(RightTextProperty);
}
public static void SetLeftText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(LeftTextProperty, value);
}
public static void SetRightText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(RightTextProperty, value);
}
private static void LeftTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.LeftText = e.NewValue as string;
}
private static void RightTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.RightText = e.NewValue as string;
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.checkBox.Width = e.NewSize.Width;
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
Storyboard story = dockPanel.Resources["ShowLeftStoryboard"] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
// must manipulate this in code behind, binding does not work,
// also it cannot be inside of a control template
// because storyboards in control templates become frozen and cannot be modified
sk.Value = cb.Width / 2;
if (cb.IsChecked == true)
{
story.Begin(cb, cb.Template);
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowLeftStoryboard");
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowRightStoryboard");
}
private void RunAnimation(string storyboard)
{
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
if (dockPanel != null)
{
Storyboard story = dockPanel.Resources[storyboard] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
story.Begin(cb, cb.Template);
}
}
}
}
IValueConverter
namespace YOURNAMESPACE.ValueConverters
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
/// <summary>
/// Does basic math operations, eg. value = 60 parameter = "*2 + 1", result = 121
/// </summary>
/// <seealso cref="System.Windows.Data.IValueConverter" />
[ValueConversion(typeof(double), typeof(double))]
public class MathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value;
string mathExpression = d.ToString() + parameter;
if (mathExpression.Contains("^"))
{
throw new Exception("Doesn't handle powers or square roots");
}
DataTable table = new DataTable();
table.Columns.Add("expression", typeof(string), mathExpression);
DataRow row = table.NewRow();
table.Rows.Add(row);
double ret = double.Parse((string)row["expression"]);
if (ret <= 0)
{
return 1d;
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// not implemented
return null;
}
}
}
example usage:
<chart:SliderControl x:Name="sliderControl"
LeftText="{Binding Path=LeftTextProperty}"
RightText="{Binding Path=RightTextProperty}"
Grid.Row="0"
IsLeftVisible="{Binding Path=IsLeftVisible, Mode=TwoWay}"
Height="35" />

Button Visibility According To Listbox ListboxItem Selection

I have a situation as follows
I want to hide delete button when no item is selected & show when something is selected.
Also hide that button if user selects an item & clicks elsewhere (which represents that user is no longer working with the listbox).
I tried LostFocus events, checking SelectedIndex etc. but no success. Any idea how to do this?
private void ListBoxItem_LostFocus(object sender, RoutedEventArgs e)
{
if (button.IsFocused != true) // checking if user has selected an item & clicking on button (valid action)
{
listbox.SelectedIndex = -1;
}
}
private void listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listbox.SelectedIndex == -1)
{
button.Visibility = Visibility.Hidden;
}
else
{
button.Visibility = Visibility.Visible;
}
}
Joseph's answer is correct but you don't need a converter.
You can achieve the same result with a simple trigger:
<ListBox>
...
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="YOUR_BUTTON'S_NAME" Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Edit: It's indeed impossible to use TargetName in a Style Setter.
So, the closest solution I could make work is to create a DataTrigger:
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyList, Path=SelectedItem}"
Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock Text="Button"/>
</Button>
<ListBox x:Name="MyList">
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
</ListBox>
</DockPanel>
For the first half of the question the best way is to use a converter like so :
1. add a Converter class :
public class VisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null ? Visibility.Hidden : Visibility.Visible);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
2.add it to the Window resources:
<Window.Resources>
<wpfApplication:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
3. and use it to hide/show your button :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox HorizontalAlignment="Center" x:Name="listBox">
<ListBoxItem Content="Fist"/>
<ListBoxItem Content="Second"/>
<ListBoxItem Content="Third"/>
<ListBoxItem Content="Fourth"/>
</ListBox>
<Button Content="delete" Grid.Row="1" HorizontalAlignment="Center" Visibility="{Binding SelectedItem,ElementName=listBox,Converter={StaticResource VisibilityConverter}}"/>
</Grid>
And for the second half of the question, you must handle the MouseDown event in your window:
MouseDown="MainWindow_OnMouseDown"
and add the following handler (that will reset the SelectedItem of the list to null)
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
listBox.SelectedItem = null;
}
Run this code separately and try.it is working for me here.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<ListBox x:Name="lst">
<ListBox.Triggers>
<EventTrigger RoutedEvent="GotFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="LostFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBox.Triggers>
<ListBoxItem>ListBoxItem1</ListBoxItem>
<ListBoxItem>ListBoxItem2</ListBoxItem>
<ListBoxItem>ListBoxItem3</ListBoxItem>
<ListBoxItem>ListBoxItem4</ListBoxItem>
</ListBox>
<Button x:Name="DeleteButton" Content="Delete" Height="30" Width="100">
<Button.Style>
<Style BasedOn="{StaticResource alredayDefinedStyleKey}" TargetType="Button">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem, ElementName=lst}" Value="{x:Null}" >
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<ListBox Grid.Column="1">
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
</ListBox>
</Grid>
LostFocus will only fire, if the element loses Logical focus. Use LostKeyboardFocus event. Then you're one step closer.

How to specify a DependencyProperty as a resource

I'd like to create buttons programmatically in WPF which grows to a specific size when IsMouseOver == true, and shrink to its original size when IsMouseOver == false.
To achieve this behaviour, I derived the Button class, and added a DependencyProperty:
public class ScalableButton : Button
{
public Storyboard MouseOutAnimation
{
get { return (Storyboard)GetValue(MouseOutAnimationProperty); }
set { SetValue(MouseOutAnimationProperty, value); }
}
// Using a DependencyProperty as the backing store for MouseOutAnimation. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MouseOutAnimationProperty =
DependencyProperty.Register("MouseOutAnimation", typeof(Storyboard), typeof(ScalableButton), new UIPropertyMetadata(null));
public ScalableButton(double originalScale, Style style)
: base()
{
CreateMouseOutAnimation(originalScale);
RenderTransform = new ScaleTransform(originalScale, 1, 0.5, 0.5);
RenderTransformOrigin = new Point(0.5, 0.5);
Style = style;
ApplyTemplate();
}
private void CreateMouseOutAnimation(double originalScale)
{
DoubleAnimation animX = new DoubleAnimation();
animX.To = originalScale;
animX.Duration = TimeSpan.FromMilliseconds(200);
DoubleAnimation animY = new DoubleAnimation();
animY.To = 1;
animY.Duration = TimeSpan.FromMilliseconds(200);
Storyboard sb = new Storyboard();
sb.Children.Add(animX);
sb.Children.Add(animY);
Storyboard.SetTarget(sb, this.RenderTransform);
Storyboard.SetTargetProperty(animX, new PropertyPath(ScaleTransform.ScaleXProperty));
Storyboard.SetTargetProperty(animY, new PropertyPath(ScaleTransform.ScaleYProperty));
MouseOutAnimation = sb;
}
}
Then, I created a Style with a ControlTemplate to make a custom appearance for my buttons, and added a Storyboard resource to scale up them, and another to scale down and here is the problem:
<Style TargetType="{x:Type rgw:ScalableButton}" x:Key="EllipseWithText" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type rgw:ScalableButton}">
<ControlTemplate.Resources>
<Storyboard x:Key="MouseOverAnimation">
<DoubleAnimation Storyboard.TargetName="ButtonGrid" Storyboard.TargetProperty="RenderTransform.ScaleX" To="1.2" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="ButtonGrid" Storyboard.TargetProperty="RenderTransform.ScaleY" To="1.2" Duration="0:0:1" />
</Storyboard>
<Storyboard x:Key="MouseOutAnimation">
<!-- This would be the one which scales down -->
</Storyboard>
</ControlTemplate.Resources>
<Grid x:Name="ButtonGrid" RenderTransform="{TemplateBinding RenderTransform}" RenderTransformOrigin="0.5, 0.5">
<Ellipse x:Name="ButtonEllipse" Height="50" Width="150" Fill="#00000000" StrokeThickness="1" Stroke="Black" />
<TextBlock x:Name="ButtonText" Width="150" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MouseOverAnimation}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource MouseOutAnimation}" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, how can I specify the ScalableButton.MouseOutAnimation as a resource?
Or is there any other way the achieve the scalable button?
There is no need for a derived Button.
Do not set the To property in the ExitActions animations. This will automatically animate from the current property values back to the original property values. Note that in the example below i initiallly set ScaleX = 0.5.
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid RenderTransformOrigin="0.5, 0.5">
<Grid.RenderTransform>
<ScaleTransform x:Name="scale" ScaleX="0.5"/>
</Grid.RenderTransform>
<Ellipse Height="50" Width="150" Fill="Transparent" StrokeThickness="1" Stroke="Black" />
<TextBlock Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" TextWrapping="Wrap" Text="{TemplateBinding Content}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scale"
Storyboard.TargetProperty="ScaleX"
To="1.2" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetName="scale"
Storyboard.TargetProperty="ScaleY"
To="1.2" Duration="0:0:0.3"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scale"
Storyboard.TargetProperty="ScaleX"
Duration="0:0:0.2"/>
<DoubleAnimation
Storyboard.TargetName="scale"
Storyboard.TargetProperty="ScaleY"
Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EDIT:
Although MinWidth is certainly not a scale factor: you could bind like the following.
<ScaleTransform ScaleX="{Binding MinWidth,
RelativeSource={RelativeSource Mode=TemplatedParent}}"/>

Categories

Resources