I am using a Telerik RadTimeline control in my project, I have a template for the TimelineItemTemplate:
<telerik:RadTimeline x:Name="myTimeline"
PeriodStart="{Binding PeriodStart}"
PeriodEnd="{Binding PeriodEnd}"
VisiblePeriodStart="{Binding VisiblePeriodStart, Mode=TwoWay}"
VisiblePeriodEnd="{Binding VisiblePeriodEnd, Mode=TwoWay}"
StartPath="StartDate"
DurationPath="Duration"
GroupPath="Title"
GroupExpandMode="Multiple"
ItemsSource="{Binding myData}"
TimelineItemTemplate="{StaticResource myTemplate}"
ScrollMode="ScrollOnly"
IsSelectionEnabled="True" SelectionMode="Extended"
DataContext="{Binding ElementName=vm}">
</telerik:RadTimeline>
My template is the next:
<DataTemplate x:Key="myTemplate">
<controls:CustomTimelineControl Style="{StaticResource myStyle}"/>
</DataTemplate>
And my style:
<Style x:Key="myStyle" TargetType="controls:CustomTimelineControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:CustomTimelineControl">
<Border Height="{Binding Source={StaticResource odpSettings}, UpdateSourceTrigger=, Converter={StaticResource visibleToHeightConverter3}, ConverterParameter=Largos}" Margin="{Binding Source={StaticResource odpSettings},Path=ItemBorderMargin,Mode=OneWay}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="largosHeatItem" Storyboard.TargetProperty="Background" Duration="0.00:00:00.05">
<DiscreteObjectKeyFrame KeyTime="0.00:00:00.0" Value="{StaticResource HeatItem_Background_MouseOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="largosHeatItem" Storyboard.TargetProperty="Background" Duration="0.00:00:00.05">
<DiscreteObjectKeyFrame KeyTime="0.00:00:00.0" Value="{StaticResource HeatItem_Background_MouseOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="largosHeatItem" Height="{Binding Source={StaticResource odpSettings}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource visibleToHeightConverter2}, ConverterParameter=Largos}"
Margin="0, 0, 0, 0" Background="{Binding DataItem.Status, Converter={StaticResource typeToColorConverter}}">
<TextBlock Text="{Binding DataItem.HeatId}"
FontFamily="Segoe UI"
Foreground="{StaticResource HeatItem_Foreground}"
FontSize="{Binding Source={StaticResource odpSettings}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource visibleToFontSizeConverter}}"
Height="{Binding Source={StaticResource odpSettings}, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource visibleToHeightConverter1}, ConverterParameter=Largos}"
TextAlignment="Center"/>
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
I have converters for the borders height, textblock size and height, as value I am sending an object that contains several properties in order to set the correct value, for example:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
AppSettings objSettings = value as AppSettings;
double val = 0;
if (parameter != null && parameter is string)
{
switch((string)parameter)
{
case "Planos":
if(objSettings.BothGantt)
val= objSettings.PlanosItemHeight1;
else
val= objSettings.PlanosItemHeightMax1;
break;
case "Largos":
if(objSettings.BothGantt)
val= objSettings.LargosItemHeight1;
else
val= objSettings.LargosItemHeightMax1;
break;
}
}
return val;
}
opdSettings is an ObjectDataProvider:
<ObjectDataProvider x:Key="odpSettings" ObjectType="{x:Type src:AppSettings}"/>
When the application is loaded, the control is shown correctly, but then when the appication is running I need to change some properties of odpSettings that affects the Timeline layout but it doesn't show the new values.
Is there a way to "refresh" the converters binding so the control shows the changes? I tried to use UpdateSourceTrigger=PropertyChanged but it didn't work.
Or is there another way to accomplish my goal?
Thanks in advance.
Alberto
Related
this is a somewhat odd one.
I'm looking to use XAML to get an expander (or anything with checked and unchecked states such as a Togglebutton and the like) to set itself back to being collapsed/unchecked after being 'ignored' (for lack of a better term) after a given period of time.
Currently the parameters I'm using to decide if it's being 'ignored' are that
MouseOver=False and IsExpanded=True, when this trigger is raised, it'll begin a storyboard that, after about 3 seconds will set "IsExpanded" to "False".
Which is working just fine.
The problem is when you then want to RE-expand the expander - you can't, because the storyboard appears to be holding it as False.
I'm probably just doing something dumb/not considering some angle of this, but it's incredibly frustrating.
If it should turn out that it can't be done with XAML alone then that's okay, but i would prefer a purely XAML solution if one exists.
To better illustrate the issue, i've made a sample project you can look at that can be downloaded Here.
Alternatively, here's the code to view:
<Window.Resources>
<Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}">
<Style.Resources>
<Storyboard x:Key="ExpanderSelfCollapseStoryboard">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="{x:Null}" Storyboard.TargetProperty="(Expander.IsExpanded)">
<DiscreteBooleanKeyFrame KeyTime="0:0:3" Value="False" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Tag)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0" Value="Not long..."/>
<DiscreteObjectKeyFrame KeyTime="0:0:3" Value="Notice you can't open the expander anymore :("/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.EnterActions>
<BeginStoryboard x:Name="ExpanderSelfCollapseStoryboard_BeginStoryboard" Storyboard="{StaticResource ExpanderSelfCollapseStoryboard}" />
</MultiTrigger.EnterActions>
<MultiTrigger.Conditions>
<Condition Property="IsExpanded" Value="True" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Expander x:Name="localExpander"
Background="DodgerBlue"
Header="Expand me!"
Style="{StaticResource ExpanderStyle}"
Tag="Up there!">
<Border Height="200"
Margin="5"
Background="Orange">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Bold"
Foreground="White"
Text="Now move your mouse over the whitespace for a few seconds please."
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Expander>
<TextBlock Grid.Row="1"
VerticalAlignment="Center"
FontSize="22"
Foreground="LightGray"
Text="{Binding ElementName=localExpander,
Path=Tag}"
TextAlignment="Center" />
</Grid>
I'd be thankful for any help or advice anyone can render.
Thanks,
-- Logan
I have a LongListSelector which is populated with some items. Each item has a submenu which can be visible or collapsed using a sliding animation. The problem is that the animation is extremely slow depending on which item you tap in the list. At the start and the end it's slow, in the middle it's smooth. I suspect that each animation frame invalidates the longlistselector which means the entire visual tree must update it's layout.
I use a datatrigger to start the animation. Because of that I can see that the triggers on lots of other items also get fired which indicates to me they are being redrawn. It also shows that when you tap in the middle of list a lot less other items get triggered as well. weird...
I hosted the testproject on github:
https://github.com/jessetabak/wpanimationproblem
The LongListSelector:
<phone:LongListSelector x:Name="LongList" Margin="0" Padding="0" ItemsSource="{Binding Items}"
HorizontalAlignment="Stretch" Background="Transparent">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<wegGooiApp2:ItemView />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
The ItemView:
<UserControl.Resources>
<wegGooiApp2:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<Storyboard x:Key="ShowMenu">
<DoubleAnimation Storyboard.TargetProperty="(Grid.Height)"
Storyboard.TargetName="Submenu"
From="0" To="70" Duration="0:0:0.25" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Submenu"
Storyboard.TargetProperty="(Grid.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HideMenu">
<DoubleAnimation Storyboard.TargetProperty="(Grid.Height)"
Storyboard.TargetName="Submenu"
From="70" To="0" Duration="0:0:0.25" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Submenu"
Storyboard.TargetProperty="(Grid.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0.25">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- TEST ITEM -->
<Border Height="70" BorderBrush="Red" Background="Transparent" BorderThickness="1" HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Test Item" HorizontalAlignment="Stretch" FontSize="42" />
<Button Content="v" Grid.Column="1" Tap="Button_Tap" Tag="{Binding}">
</Button>
</Grid>
</Border>
<!-- SUBMENU -->
<Border x:Name="Submenu" Grid.Row="1" BorderBrush="Green" BorderThickness="1"
Visibility="{Binding SubMenuIsVisible, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneTime}"
Background="Green" Height="0" Margin="0 0 0 0">
<i:Interaction.Triggers>
<ec:DataTrigger Binding="{Binding SubMenuIsVisible}" Value="True">
<ec:CallMethodAction MethodName="MenuEnabled"
TargetObject="{Binding ElementName=ThisUserControl, Mode=OneWay}" />
</ec:DataTrigger>
<ec:DataTrigger Binding="{Binding SubMenuIsVisible}" Value="False">
<ec:CallMethodAction MethodName="MenuDisabled"
TargetObject="{Binding ElementName=ThisUserControl, Mode=OneWay}" />
</ec:DataTrigger>
</i:Interaction.Triggers>
<TextBlock Text="SubMenu" FontSize="42" />
</Border>
</Grid>
</UserControl>
The ItemView codebehind:
public partial class ItemView : UserControl
{
private Storyboard _showStoryboard;
private Storyboard _hideStoryboard;
public ItemView()
{
InitializeComponent();
_showStoryboard = (Storyboard) this.Resources["ShowMenu"];
_hideStoryboard = (Storyboard) this.Resources["HideMenu"];
Debug.WriteLine("ItemView CONSTRUCTED");
}
private void Button_Tap(object sender, GestureEventArgs e)
{
var button = (Button)sender;
var viewModelItem = (ItemViewModel)button.Tag;
viewModelItem.SubMenuIsVisible = !viewModelItem.SubMenuIsVisible;
}
public void MenuEnabled()
{
Debug.WriteLine("MENU ENABLED!");
if (Submenu.Visibility == Visibility.Collapsed)
{
_showStoryboard.Begin();
}
}
public void MenuDisabled()
{
Debug.WriteLine("MENU DISABLED!");
if (Submenu.Visibility == Visibility.Visible)
{
_hideStoryboard.Begin();
}
}
private void ThisUserControl_LayoutUpdated(object sender, EventArgs e)
{
//Debug.WriteLine("ITEMVIEW LAYOUT UPDATED!");
}
}
And what it looks like:
/edit 1
I tried turning it into an independent animation using a ScaleTransform, but this won't animate the surrounding ui elements. To fix this you can use a LayoutTransform instead of the standard RenderTransform. After some tweaking this worked quite nice, but the layouttranform turned it back in a slow depenpendent animation...
You are correct that changing the UserControl height causes a large portion of the visual tree to be invalidated, but this is required, and by design. The issue is that you are modifying a controls height in a storyboard to begin with, which is not an independent animation and can't run on the compositor.
Have a read of http://msdn.microsoft.com/en-us/library/windows/apps/jj819807.aspx#dependent although this is for Windows store apps (and there is EnableDependentAnimations flag in SL), the ideas remain the same. You need to figure out a way to expand items using independent animations, probably by using a ScaleTransform.
I have a button above a listbox which has it's items in a horizontal allignment. If my button has focus and I press the down key on the keyboard then the listbox get focus. If I then press key up then the listbox still has focus. Can this behaviour be changed so when I press key up my button has focus again?
EDIT: I should add I'm using MVVM so would like to keep the solution out of codebehind if possable.
Below is the complete code for my listbox.
<ListBox Grid.Row="2" ItemsSource="{Binding Movies}" TextSearch.TextPath="Title" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled">
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="poster">
<Image x:Name="posterImage" RenderOptions.BitmapScalingMode="LowQuality" Source="{Binding Poster}" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualHeight}">
</Image>
<TextBlock Text="{Binding PosterOverlayText}" Style="{DynamicResource PosterOverlayText}" Width="{Binding ActualWidth, ElementName=posterImage}"/>
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="st" ScaleX="0.85" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
</TransformGroup>
</Grid.LayoutTransform>
<Grid.InputBindings>
<MouseBinding Command="{x:Static Commands:MediaFiles.PlaySelectedMovie}" Gesture="LeftDoubleClick" />
</Grid.InputBindings>
</Grid>
<DataTemplate.Resources>
<CubicEase x:Key="ease" EasingMode="EaseOut"/>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.3"
EasingFunction="{StaticResource ease}"
Storyboard.TargetName="st"
Storyboard.TargetProperty="ScaleX"
To="1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.3"
EasingFunction="{StaticResource ease}"
Storyboard.TargetName="st"
Storyboard.TargetProperty="ScaleX"
To="0.85"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent"/>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.InputBindings>
<KeyBinding Command="{x:Static Commands:MediaFiles.PlaySelectedMovie}" Gesture="ENTER"/>
</ListBox.InputBindings>
</ListBox>
Don't know if there is a straight-forward MVVM accepted solution to this problem. The reason is that ListBox actually uses the key up and key down events to navigate through the list items (which is the desired effect most of the times) whereas the buttons will let the events go to the FocusManager class.
However, as for everything out there, there are workarounds so here's how I would do it:
First of all you need to intercept the Key-Down event and look for directional inputs (up-down-left-right). I would think that you would still like to navigate with the keys in your list so, in the event handler I would make sure to test if I am on the first (or last) element and then use the MoveFocus() method with TraversalRequest - FocusNavigationDirection.Up / Down / Next / Previous to move the focus.
Last, to ensure your code-behind is clean, create a new ListBox derived control and have your event there. Actually, in the derived control, don't use the event, overide OnKeyDown method. This way, you are writing the focus logic in your custom control and not the View.
Code:
protected override void OnKeyDown(KeyEventArgs e)
{
if ((e.Key == Key.Up ||e.Key == Key.Left) && SelectedIndex == 0)
{
//.Previous will navigate correctly to the last element
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
return;
}
if (e.Key == Key.Down && this.SelectedIndex == this.Items.Count - 1)
{
//.Next will navigate to the first item in the list rather than skip to the next control. We will use Down or Right.
//this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
return;
}
if (e.Key == Key.Right && this.SelectedIndex == this.Items.Count - 1)
{
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
return;
}
base.OnKeyUp(e);
}
I have a WPF data trigger that is set to fire when a value is true.
I want this trigger to fire everytime this value is set to true, even if it was true before. Unfortunately it seems only to fire if the value is changed from true to false or vise versa. My underlying data model is firing the PropertyChanged event of INotifyPropertyChanged even if the value is set to true twice in succession but the Trigger doesn't seem to pick this up.
Is there anyway to make the trigger run regardless of whether the bound value has changed?
Some of you have asked for the code so here it is. Interesting to note that converters will be called each time. The problem is more specific to running an animation.
Update
If I change my code to reset the value to false and then back to true again it does fire the animation. Obviously this is not ideal and doesn't make the code nice to read. I'm hoping there is a better way to do this.
Any help greatly appreciated.
WPF code
<Grid>
<Grid.Resources>
<Storyboard x:Key="AnimateCellBlue">
<ColorAnimation Storyboard.TargetProperty="Background.Color" From="Transparent" To="Blue" Duration="0:0:0.1" AutoReverse="True" RepeatBehavior="1x" />
</Storyboard>
</Grid.Resources>
<TextBox Name="txtBox" Text="{Binding DataContext.DisplayText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsTrue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="BidSizeUpStoryB" Storyboard="{StaticResource AnimateCellBlue}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
Code Behind:-
public partial class MainWindow : Window
{
private DataItem _dataItem;
private DispatcherTimer _dispatcherTimer;
public MainWindow()
{
InitializeComponent();
_dataItem = new DataItem();
_dataItem.DisplayText = "Testing";
_dataItem.IsTrue = true;
this.DataContext = _dataItem;
_dispatcherTimer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, TimerCallbackHandler, Dispatcher);
}
private void TimerCallbackHandler(object s, EventArgs args)
{
Console.WriteLine("In Timer");
_dataItem.IsTrue = true;
_dataItem.DisplayText = "Timer " + DateTime.Now.Ticks;
}
}
DataItem:-
public class DataItem : INotifyPropertyChanged
{
private bool _isTrue;
private string _displayText;
public bool IsTrue
{
get { return _isTrue; }
set
{
_isTrue = value;
NotifyPropertyChanged("IsTrue");
}
}
public string DisplayText
{
get
{
return _displayText;
}
set
{
_displayText = value;
NotifyPropertyChanged("DisplayText");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
The trigger will be fire irrespective of the value set. Whenever PropertyChanged event is raised for the property binded with trigger, the trigger will be get called. Here's the sample on which i have verified -
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.IsTrue, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},
Converter={StaticResource MyConverter}}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
I put the breakpoint on my converter and it got called whenever i raise PropertyChanged event for property IsTrue.
There msut be some other issue in your code. Can you please show bit of your code where you are facing this issue??
Update: it seems that my first answer was dependent on my ValueConverters. So, I did a bit more research and discovered ConditionalBehavior and PropertyChangedTrigger
Here's one that I tested.
<UserControl
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="WpfControlLibrary1.UserControl1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded"/>
</UserControl.Triggers>
<Grid>
<i:Interaction.Triggers>
<!--Use multiple PropertyChangedTriggers to handle multiple conditions-->
<!--True State-->
<ei:PropertyChangedTrigger Binding="{Binding DataContext.IsTrue,
RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}" >
<i:Interaction.Behaviors>
<!--This is just a humble demonstration of Conditional Behavior, it's has so much potential that you can replicate complex if conditions-->
<ei:ConditionBehavior>
<ei:ConditionalExpression ForwardChaining="And">
<ei:ComparisonCondition Operator="Equal"
LeftOperand="{Binding DataContext.IsTrue, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"
RightOperand="True"
/>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<!--I'm not sure why, but I needed to apply the default state first so that the true state plays it's storyboard.-->
<!--If you don't do this, this behaves like a data trigger.-->
<ei:GoToStateAction StateName="DefaultState"/>
<ei:GoToStateAction StateName="TrueState"/>
</ei:PropertyChangedTrigger>
<!--False state-->
<ei:PropertyChangedTrigger Binding="{Binding DataContext.IsTrue,
RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}" >
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression ForwardChaining="And">
<ei:ComparisonCondition Operator="Equal"
LeftOperand="{Binding DataContext.IsTrue, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"
RightOperand="False"
/>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:GoToStateAction StateName="DefaultState"/>
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="DefaultState"/>
<VisualState x:Name="TrueState">
<Storyboard Storyboard.TargetName="txtBox" >
<ColorAnimation Storyboard.TargetProperty="Background.Color" From="Transparent" To="Blue" Duration="0:0:0.1" AutoReverse="True" RepeatBehavior="1x" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="txtBox" Text="{Binding DataContext.DisplayText, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"/>
</Grid>
</UserControl>
You can use Visual State Groups for this. What's more, behavior property and value can both be databound!
I did this quickly in blend and did not test. this is similar to my solution.
<UserControl
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="WpfControlLibrary1.UserControl1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding DataContext.IsTrue,
RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"
Value="True"
TrueState="TrueState"/>
</i:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="DefaultState"/>
<VisualState x:Name="TrueState">
<Storyboard Storyboard.TargetName="txtBox" >
<ColorAnimation Storyboard.TargetProperty="Background.Color" From="Transparent" To="Blue" Duration="0:0:0.1" AutoReverse="True" RepeatBehavior="1x" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="txtBox" Text="{Binding DataContext.DisplayText, RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}}"/>
</Grid>
</UserControl>
I'm trying to get a message panel animated in WPF but has so far achieved no success.
This is the situation:
I have a user control with a StackPanel containing an ItemsControl bound to an (observable) collection in the control's View Model object (ViewModel.Messages).
When I need to present the user with messages I ad those (as MessageVM instances) to the observable collection.
The ItemsControl's visibility is bound to an integer property called ViewModel.CountVisibleMessages and there's a converter taking care of translating 0 to Visibility.Hidden and positive values to Visibility.Visible.
This works just fine. When a message gets added to the collection the StackPanel automatically becomes visible and as the user (or a timer) removes the last message it gets hidden. The StackPanel height is automatically adjusted to fit all messages of course.
To make everything look nicer I would prefer it if the StackPanel resized itself using an animation running for, say, 300 ms. (Ultimately I would also like it to accelerate and deccelerate but that's beyond my ambition right now.
I have experimented for a few hours now but I feel I'm not even close.
Below is my current (not even close to working) XAML at the moment:
<StackPanel Orientation="Vertical"
VerticalAlignment="Top"
Visibility="{Binding CountVisibleMessages, Converter={StaticResource IntToVisibility}}"
Height="Auto"
Background="{DynamicResource HmiBackColorLightBrush}">
<StackPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CountVisibleMessagesChanged}" Value="True" ><!-- I suppose I shopuld've used a Routed Event here but I just needed to get it triggered -->
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Margin.Bottom"
From="100" <!-- Just a made up value to test the concept -->
To="0"
Duration="0:0:0:3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<ItemsControl ItemsSource="{Binding Messages}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Style="{DynamicResource Message}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Text}" Margin="3" Style="{Binding MessageType, Converter={StaticResource MessageTypeToStyle}, ConverterParameter={x:Type TextBlock}}" /> <!-- using dynamic styling here -->
<RadioButton Grid.Column="1" Style="{DynamicResource HmiCloseMessageButton}" IsChecked="{Binding IsVisible, Converter={StaticResource BoolToVisibility}, ConverterParameter=true}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
(I do realize the above XAML won't get the StackPanel to auto-resize slowly. It's just an experiment to get anything happening).
This can't be too difficult I suppose (it's a pretty standard UI behavior in many programs) so I'd appreciate it if anyone could point me in the right directions.
Cheers
Are you sure you want to adjust the bottom margin? The stack panel VerticalAlignment is top. If you want to change the Height then bind your StoryBoard Property to Height. Do you know if your StoryBoard is firing?
The key point is ExitActions are necessary for EnterAction based dataTrigger animations. So the following seems to be working in my case ....
<StackPanel Grid.Row="0" Height="100" x:Name="MyGrid">
<StackPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=MyCheckBox,
Path=IsChecked}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.Target="{Binding
RelativeSource={RelativeSource
AncestorType={x:Type
StackPanel}},
BindsDirectlyToSource=True}"
Storyboard.TargetProperty="Height"
From="100"
To="200"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.Target="{Binding
RelativeSource={RelativeSource
AncestorType={x:Type
StackPanel}},
BindsDirectlyToSource=True}"
Storyboard.TargetProperty="Height"
To="100"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
<CheckBox x:Name="MyCheckBox" Grid.Row="1" />
Let me know if this helps.