WPF Control Template bind to Child Control - wpf

In a TreeViewItem control template I need to bind a visual state to a child control but I cannot figure out the binding syntax.
In the TreeView's HierarchicalDataTemplate I have:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel>
<Image Name="imgPicture" Source=".."/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
I would like to bind a visual state animation to imgPicture in the control template.
In the TreeViewItem's control template I have:
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header}"
Storyboard.TargetProperty="MaxHeight"
To="100"
Duration="0"/>
</Storyboard>
</VisualState>
But the Storyboard.TargetName binds to the StackPanel (since that is the header of the TreeViewItem), and I need it to bind to the control inside the StackPanel (the imgPicture) so that I can change the control's property. Is there a way that I can do this by using the xaml binding syntax? Thank you very much for your help.

You can create a Binding by using the ElementName property (imgPicture) or set the Storyboard.TargetName to imgPicture.

Related

WPF smooth transition for databound property updates

I have a control that has its margin bound to a property of my view model:
<Grid Margin="{Binding Path=Property1, Converter={StaticResource Converter1}}"></Grid>
How do I get a smooth animation between successive updates to the Margin property? I want the margin to slide for a short amount of time instead of a discrete jump. Preferably a xaml solution.
Edit:
This is different than the other questions on this site, because I would need the "From" in a thickness animation to be bound to the previous value, and "To" to be bound to the updated value. It seems like a hack to just add another property to the view model for this.
Found the solution; the animation only needs to bind to the "From" and it will animate the way I want.
<Grid Margin="{Binding Path=Property1,
NotifyOnTargetUpdated=True,
Converter={StaticResource Converter1}}">
<Grid.Triggers><EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard><StoryBoard>
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Duration="00:00:00.5"
From="{Binding Path="Property1" Converter={StaticResource Converter1}}"/>
</StoryBoard></BeginStoryboard>
</EventTrigger></Grid.Triggers>
</Grid>
You can use ThicknessAnimation:
<BeginStoryboard>
<Storyboard>
<!-- BorderThickness animates from left=1, right=1, top=1, and bottom=1 to
left=28, right=28, top=14, and bottom=14 over one second. -->
<ThicknessAnimation
Storyboard.TargetProperty="Margin"
Duration="0:0:1.5" FillBehavior="HoldEnd" From="1,1,1,1" To="28,14,28,14" />
</Storyboard>
</BeginStoryboard>
You just need to bind the properties From and To

Using ObservableCollection firing Binding.TargetUpdated only on single item

I have three Textblocks with the Text Property bound to the items of an ObservableCollection:
<TextBlock Style="{StaticResource FadeInTextBlock}" Text="{Binding Path=CurrentAnswers[0], NotifyOnTargetUpdated=True}" />
<TextBlock Style="{StaticResource FadeInTextBlock}" Text="{Binding Path=CurrentAnswers[1], NotifyOnTargetUpdated=True}" />
<TextBlock Style="{StaticResource FadeInTextBlock}" Text="{Binding Path=CurrentAnswers[2], NotifyOnTargetUpdated=True}" />
The Property with INotifyPropertyChanged implemented:
public ObservableCollection<Answer> CurrentAnswers
{
get { return currentAnswers; }
set { currentAnswers = value; RaisePropertyChanged("CurrentAnswers"); }
}
Each Textblock uses the same style containing a trigger for the Binding.TargetUpdated event which fades in the actual text:
<Style x:Key="FadeInTextBlockTwo" TargetType="TextBlock">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:0" To="0.0"/>
<DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:1" From="0.0" To="1.0" BeginTime="0:0:0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
When I change ONE item in the ObservableCollection ALL Textblocks are firing the event and do the fade in:
CurrentAnswer[1] = "New Text";
// Textblock 1 - 3 do the fade in animation,
// even if only Textblock 2 has been updated
How can I limit the animation to only the Textblock whose bound value has been updated?
When you're using indexes you bind to indexer property of your collection and UI does not know which index has changed it is just notified that indexer property has changed without specifying which index so, in your case, it refreshes all 3 TextBlocks raising TargetUpdated event. What happens is ObservableCollection raises PropertyChanged event with Binding.IndexerName as property name. To solve your problem instead of using 3 TextBlocks you can use ItemsControl
<ItemsControl ItemsSource="{Binding CurrentAnswers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource FadeInTextBlock}" Text="{Binding NotifyOnTargetUpdated=True}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
this will repeat TextBlock as many time as many items you have in CurrentAnswers

Animate DependencyProperty on a UserControl displayed through a ContentPresenter

In WPF, I have a ListBox with the list made up of UserControls. The controls are meant to navigate to different screens in the application. Each UserControl (called NavigationButton) has an icon and text. The icons are mostly combinations of multiple Path objects, so each icon is it's own UserControl, and they are being displayed using a ContentPresenter. I want to be able to animate the color of the icon depending on different states of the screen, but have tried a lot of options and have been unable to do this.
Here is a stripped down version of NavigationButton:
<DockPanel Margin="12,0,12,0">
<!-- Icon -->
<ContentPresenter x:Name="Content_Icon" Content="{Binding}" Width="20"/>
<!-- Text -->
<Grid Margin="9,0,0,0">
<TextBlock x:Name="TextBlock_Text" Text="{Binding ScreenName, Converter={StaticResource StringToStringUpperConverter}}" VerticalAlignment="Center"
FontSize="15" Foreground="#FFF2F2F2" />
</Grid>
Basically, I need to animate a property on the ContentPresenter, but don't know how to access it.
Here is the ListBox hosting the NavigationButtons:
<ListBox DockPanel.Dock="Top" ItemsSource="{Binding ScreenViewModels}"
SelectedItem="{Binding SelectedScreenViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<my:NavigationButton/>
</DataTemplate>
</ListBox.ItemTemplate>
I have created a base UserControl (called IconBaseControl) that all of these icon UserConrols can inherit. The base control has a Brush DependencyProperty, called IconFill. The parts of the paths on the icon that can change are bound to this property:
<Path Data="<data>" Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:IconBaseControl}}, Path=IconFill}"
I know the binding is working correctly because the colors change when I change the default color on the UserControl. Ideally I want to use a VisualStateManager, because there will be many different states. So, I have a VisualStateManager on NavigationButton, the UserControl containing the ContentPresenter that hosts the icon (all UserControls that inherit IconBaseControl), called Content_Icon. I tried something like this in one of the states:
<VisualState x:Name="Deselected">
<Storyboard>
<ColorAnimation Storyboard.TargetName="TextBlock_Text" Storyboard.TargetProperty="Foreground.Color"
To="#FF5e5e5e" Duration="0"/>
<ColorAnimation Storyboard.TargetName="Content_Icon" Storyboard.TargetProperty="IconFill"
To="#FF5e5e5e" Duration="0"/>
</Storyboard>
</VisualState>
But I get the following error:
InvalidOperationException: Cannot resolve all property references in the property path 'IconFill'. Verify that applicable objects support the properties.
I also tried binding the property of the storyboard with something like this:
Storyboard.TargetProperty="(IconBaseControl.IconFill)
But get this error:
IconBaseControl is not supported in a Windows Presentation Foundation (WPF) project.
I have also tried messing around in code behind but cannot figure out how to convert the ContentPresenter to an IconBaseControl. I figured the ContentTemplate property would be the way to go but it's Nothing.
Any suggestions on how to animate this property? Open to pretty much anything :) I'm coding in VB.Net but any C# suggestions are fine too.
Thanks in advance.
EDIT: Included code for NavigationButton
I find that creating sub-classes of WPF controls can get messy and isn't necessary unless it is a very advanced problem. In my opinion creating the IconBaseControl as a child of UserControl is overkill in your scenario.
Here's my suggestion assuming you are using MVVM: create the IconBaseControl as a normal UserControl. Just create a IconControl.xaml with IconControl.xaml.cs code behind file just like you would any other view.
Here is an example of what you would have inside IconControl:
<UserControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="#FF5e5e5e" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="White" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Source="Icon.jpeg" />
<TextBlock Text="{Binding PageName}" Grid.Column="1" />
</Grid>
</UserControl>`
Notice that the background of the surrounding grid will change based on a binding to a value called IsSelected on the DataContext. So at this point you need to create a ViewModel called IconControlViewModel.cs that has the IsSelected boolean exposed as a dependency property.
Finally the view that contains these navigation buttons:
<UserControl>
<ItemsControl ItemsSource="{Binding ListOf_IconControlViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type IconControlViewModel}">
<local:IconView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
Notice the DataTemplate that tells the ItemsControl what to render when it sees a IconControlViewModel in the ItemsSource list. This is how I would design it using the MVVM pattern. I hope this helps and let me know if you need clarification on my answer, or it's way off.
Cheers,
Eric

WPF VisualStateManager - How to animate properties inside a templated child

I have a UserControl that contains an ItemsControl with a custom ItemsPanel, with a dependency property called "MaxColumns". I'd like to define a VisualState (at the UserControl level) that can animate the "MaxColumns" property on the custom panel.
In essence, the XAML looks something like:
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MyCoolState">
<VisualState x:Name="Normal" />
<VisualState x:Name="NotNormal">
<Storyboard>
<Int32Animation Duration="0"
Storyboard.TargetName="Details"
Storyboard.TargetProperty="(ItemsControl.ItemsPanel).(local:CoolPanel.MaxColumns)"
To="4" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateManager>
<ItemsControl x:Name="Details">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:CoolPanel x:Name="MyCoolPanel"
MaxColumns="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
However, I cannot for the life of me figure out what the right syntax is for the animation? If I use the syntax shown above, I get the error: "'ItemsPanel' property does not point to a DependencyObject in path '(0).(1)'". I'm presuming this is because it's technically pointing to a ItemsPanelTemplate?
If I refer to "MyCoolPanel" directly in the Storyboard.TargetName property, I get an error about Name scope (presumably because "MyCoolPanel" isn't in the namescope of LayoutRoot). I don't know if there is a way to qualify name scope in "TargetName"?
Does anyone have a solution for this? It seems like something that should be doable without resorting to custom attached properties? I mean, I'm not opposed to attached properties, but I feel like you ought to be able to do this directly in the XAML?
Okay, indeed the ItemsPanel is not a real object but a template with which the object is going to be created. So technically your reference is not going to work.
I've got the following about implementation:
you set some attached property on the ItemsPanel (which is anyway a template), but on the ItemsControl itself.
You bind the CoolPanel's MaxColumns to that attached property using RelativeSource FindAncestor.
Well, you could omit the attached property, and use Tag for it :-) Indeed, the ItemsControl is totally in your control, so there is no crime in abusing the Tag a little bit.
So the code would be like this:
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MyCoolState">
<VisualState x:Name="Normal" />
<VisualState x:Name="NotNormal">
<Storyboard>
<Int32Animation Duration="0"
Storyboard.TargetName="Details"
Storyboard.TargetProperty="Tag"
To="4" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateManager>
<ItemsControl x:Name="Details" Tag="3">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:CoolPanel
MaxColumns="{Binding Tag, RelativeSource={RelativeSource FindAncestor,
AncestorType=ItemsControl}}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>

Binding a Storyboard property relative to the target of the storyboard

I have a storyboard which targets an element, and binds one of its own properties to a property on another element:
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.X"
From="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ActualWidth}"
To="0"
Duration="0:0:5"/>
</Storyboard>
This storyboard works when the storyboard is stored in the resources of the window which holds the storyboard target. The 'From' value is correctly bound to the ActualWidth of the host Window instance.
However, I need to store the storyboard in my application level resources. From here, the storyboard does not seem to be able to target the window to determine the 'From' property. This is understandable as from inside <Application.Resources>, the binding won't be able to find an 'ancestor' of type Window.
I guess I need to be able to bind the 'From' value, relative to the target of the animation, rather than relative to the storyboard's DoubleAnimation.
Is this possible, and if so, how?
Here is the sample MainWindow.xaml:
<Window.Resources>
<!--This works : Storyboard correctly sets 'From' property to 'ActualWidth' of window-->
<Storyboard x:Key="localStoryBoard">
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.X"
From="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ActualWidth}"
To="0"
Duration="0:0:5"/>
</Storyboard>
</Window.Resources>
<StackPanel>
<Button
RenderTransformOrigin="0,1"
HorizontalAlignment="Left"
Content="Click me">
<Button.RenderTransform>
<TranslateTransform/>
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource centralStoryBoard}"/>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
And here is an example app.xaml:
<Application x:Class="WpfApplication3.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!--Storyboard doesn't work at all-->
<Storyboard x:Key="centralStoryBoard">
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.X"
From="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ActualWidth}"
To="0"
Duration="0:0:5"/>
</Storyboard>
</Application.Resources>
</Application>
This won't work, as the eventtrigger refers to the app.xaml version. If you change it to the local resource version, you can see it works.
The example doesn't work because when you put your storyboard to resources it has no Window ancestor. What actually RelativeSource do is search element tree backwards waiting for the node with AncestorType appear and then binds it.
When put in the Window.Resources there's actual Window back in the tree and it binds it correctly. When put to the application resources there's no Window back in the tree because it's not connected to the Window whatsoever.
If you really want to put your storyboard to the application resources you should consider giving up the idea with binding. Instead, you can check out this answer with code for a clipper, I think this is what you need - https://stackoverflow.com/a/59376318/11178539.

Resources