wpf custom stackpanel datatrigger - wpf-controls

I'm trying to create a custom StackPanel that performs an animation (scale) when a property on a viewmodel changes. I've created a Dependency Property in my derived class
public partial class TrgScaleStackPanel : StackPanel
{
#region TriggerValue DP
/// <summary>
/// Gets or sets the Value which is being displayed
/// </summary>
public bool TriggerValue
{
get { return (bool)GetValue(TriggerValueProperty); }
set { SetValue(TriggerValueProperty, value); }
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty TriggerValueProperty =
DependencyProperty.Register("TriggerValue", typeof(bool),
typeof(TrgScaleStackPanel), new PropertyMetadata(false));
#endregion
public TrgScaleStackPanel()
{
InitializeComponent();
}
}
In the XAML I have added Style with a DataTrigger that binds to the dependency property
<StackPanel x:Class="StackPanelDemo.TrgScaleStackPanel"
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"
x:Name="TrgScaleParent"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}" >
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TrgScaleParent.TriggerValue}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.25" To="1" Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.25" To="0" Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleY)"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
On my test window I have
<stackPanelDemo:TrgScaleStackPanel TriggerValue="{Binding SettingsViewVisible}">
<TextBlock>Hello</TextBlock>
<Button>Goodbye</Button>
<stackPanelDemo:TrgScaleStackPanel.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="0" />
</stackPanelDemo:TrgScaleStackPanel.LayoutTransform>
</stackPanelDemo:TrgScaleStackPanel>
It builds and runs but unfortunately, the trigger doesn't fire. I suspect it is something to do with the data context but am not really sure.
Could someone enlighten me as to where I'm going wrong

After much fiddling I discovered that I needed to use
<DataTrigger Binding="{Binding Path=TriggerValue, RelativeSource={RelativeSource Self}}" Value="True">
Unfortunately, I have got much further in creating a truly configurable StackPanel with scale animation as one runs into a
cannot freeze this storyboard timeline tree for use across thread
when one tries to bind the "To" property of a DoubleAnimation. This means that I can't configure direction at design time with a dependency property. If anyone knows of a way to get past this please let me know!!!
Another interesting issue was that unless I specified a LayoutTransform when using my custom StackPanel the animation simply doesn't fire. I have no idea why?
If anyone knows of a better way to do this please let me know. It may be my approach is wrong?

Related

UWP, Apply a converter to a ListView's item when the ListView's items source is a collection of strings

I have the following situation. I want to show a list of strings. In order to achieve that, I have a ListView bound to a collection of strings. In this collection, there are some empty strings. What I want is to show the following text when a empty string is present: "-empty-". This is what I got so far (the source code is for demonstration purpose only):
EmptyStringConverter.cs
public class EmptyStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string && string.IsNullOrWhiteSpace((string)value))
{
return "-empty-";
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:EmptyStringConverter x:Key="EmptyStringConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="ListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EmptyStringConverter}}" Margin="0,0,0,5" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var source = new[] { "", "String 1", "String 2" };
ListView.ItemsSource = source;
}
}
When a put a breakpoint in the Convert method in EmptyStringConverter class, the method is called in every single item except in the empty string. How can I achieve what I want?
Well the issue was in the place I'd check the last. It's your Legacy Binding that's causing the issue. I tried a code snippet myself and then I replaced it with yours piece by piece. The below line uses legacy binding
Text="{Binding Converter={StaticResource EmptyStringConverter}}"
Since you are using UWP you can switch to a Compile time binding that'll fix your problem your modified ListView XAML would be:
<ListView x:Name="ListView" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind Converter={StaticResource EmptyStringConverter},Mode=OneTime}" Margin="0,0,0,5" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Please pay attention at two things:
the Text section of the Textbox in the DataTemplate, it uses x:Bind instead of Binding. Please do note that compile time binding is a oneTime binding by default (unless you explicitly mention the Mode) where as the Legacy Binding is a OneWay binding by default. For More information on compile time binding follow: xBind markup extension.
If you notice the DataTemplate declaration holds a DataType property, that helps the compile time binder to know what kind of data is it expecting. For more information on DataType follow: xBind markup extension.
All that being said, I would highly recommend using the Data Binding approach instead of the ListView.ItemSource=source as with the new Compile Time binding, alot of conversions are handled by the binding engine itself leading to less code and effort on your end. I've put up a sample on github for the same you can check it out: EmptyStringDemo

Binding a DataTemplate in a UserControl

I'm writing a UserControl, which I intend to use on several pages. It should encapsulate the behaviour to be the same for all pages. But the content and the layout should be different.
On the UserControl I have a ListView whoes ItemSource is bound to a CollectionViewSource with grouping enabled.
<ListView
ItemSource="{Binding Source={StaticResource Collection}}"
ItemTemplate="{Binding GroupedDataTemplate}">
<ListView.GroupStyle>
<GroupSytele HeaderTemplate="{Binding HeaderDataTemplate}"/>
</ListView.GroupStyle>
</ListView>
The UserControl has the DependencyProperties "GroupedDataTemplate", "HeaderDataTemplate" for the layout and one "GroupedCollection" for the data.
On the page, where the UserControl is used, I defined the DataTemplates like:
<controls:MyUserControl
GroupedCollection="{Binding DataContext.MyDataCollection, ElementName=thePage}">
<controls:MyUserControl.GroupedDataTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</controls:MyUserControl.GroupedDataTemplate>
<controls:MyUserControl.HeaderDataTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
</controls:MyUserControl.HeaderDataTemplate>
</controls:MyUserControl>
My problem is, that the DataTemplate definition for "GroupedDataTemplate" works as expected, the description is shown. But for the "HeaderDataTemplate" it doesn't, it is shown only the ToString()-representation of the object.
The setter of the "HeaderDataTemplate" is called and the DataTemplate is assigned to the DependencyProperty of the UserControl.
If I replace the UserControl with the ListView itself, it works as expceted. Thus the binding works propperly to the Description and the Key, but it will only work for description if it is inside the UserControl.
For test purposes I have added a converter to the binding of the Key in the page and it is never called. I all cases, where I define a DataTemplate for an ItemTemplate (ListView or GridView) it works, but is doesn't for the HeaderTemplate of the GroupStyle.
What is my fault?
Very good question, it seems when you use Binding for the HeaderTemplate of GroupSytele, in the .g.cs file of your UserControl, it doesn't generate the update code for HeaderDataTemplate, it means when you define this HeaderDataTemplate property for example like this:
public static readonly DependencyProperty HeaderDataTemplateProperty = DependencyProperty.Register("HeaderDataTemplate", typeof(DataTemplate), typeof(UserGroupedListView), new PropertyMetadata(null));
public DataTemplate HeaderDataTemplate
{
get { return (DataTemplate)GetValue(HeaderDataTemplateProperty); }
set { SetValue(HeaderDataTemplateProperty, value); }
}
The get never gets called.
A workaround here is that you can change the Binding to x:Bind like this in your UserControl:
<ListView ItemsSource="{Binding Source={StaticResource Collection}}" ItemTemplate="{Binding GroupedDataTemplate}">
<ListView.GroupStyle>
<GroupStyle HeaderTemplate="{x:Bind HeaderDataTemplate}" />
</ListView.GroupStyle>
</ListView>
Basically you've done nothing wrong, but it seems data binding for the HeaderTemplate of GroupStyle can only work when it uses x:Bind.

UserControl embeding free XAML content

I would like to create a User Control which can embed free content.
I created a Dependency property for the content :
public sealed partial class MyUserControl : UserControl
{
public Border MyProperty
{
get { return (Border)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(Border), typeof(VisitList), new PropertyMetadata(new Border() { Height=300, Width=300 }));
...
}
So In my MainPage.xaml, I can use it with following code :
<MyUserControl>
<MyUserControl.MyProperty>
<Border x:Name="MyContent" Width="60" Height="60" Background="Pink">
... Whatever ...
</Border>
</MyUserControl.MyProperty>
</MyUserControl>
From this, I can't find what is the XAML syntax in the MyUserControl.xaml for declaring the placeholder that will be substituted by MyContent at runtime.
I tried with :
<UserControl ... >
....
<Grid ...>
<ContentPresenter Content="{TemplateBinding MyProperty}" />
</Grid>
</UserControl>
But, it crashes with message :
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in xXx.exe but was not handled in user code
WinRT information: Failed to create a 'Windows.UI.Xaml.DependencyProperty' from the text 'MyProperty'. [Line: 29 Position: 35]
(The Line: 29 Position:35 refers to Content="{TemplateBinding MyProperty}")
It sounds like you're mixing Templated Controls and UserControls. It's a bit complicated, but basically, TemplateBindings work when they're apart of the ContentTemplate of the control, as opposed to the Content itself (which is what I believe is happening here, based on the xaml you have shown).
Try changing your bindings in this way:
<UserControl x:Name="RootControl" ...>
....
<ContentControl Content="{Binding MyProperty, ElementName=RootControl}" />
....
</UserControl>
What this means is that you will need to have your UserControl implement INotifyPropertyChanged in case it needs to respond to changing Content.

WPF: Cannot bind to a custom controls dependency property

I've created a custom control with a dependency property for databinding.
The binded value should then be displayed in a text box.
This binding works properly.
The problem occurs when I implement my custom control. The grid's data context is a simple view model which contains a String property for binding.
If I bind this property to a standard wpf controls text box everything works fine.
If I bind the property to my custom control nothing happens.
After some debugging I found out that SampleText is searched in CustomControl. Of course it doesn't exist there.
Why is my property searched in CustomControl and not taken from the DataContext as it happens in scenario 1.
<Window x:Class="SampleApplicatoin.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SampleApplication"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.DataContext>
<controls:ViewModel/>
</Grid.DataContext>
<TextBox Text="{Binding SampleText}"/>
<controls:CustomControl TextBoxText="{Binding SampleText}"/>
</Grid>
</Window>
Below the XAML code of the custom control.
I use DataContext = Self to get the dependency property from code behind:
<UserControl x:Class="SampleApplication.CustomControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="87,133,0,0" TextWrapping="Wrap" Text="{Binding TextBoxText}" VerticalAlignment="Top" Width="120"/>
</Grid>
</UserControl>
The xaml.cs file just contains the dependency property:
public partial class CustomControl : UserControl
{
public static readonly DependencyProperty TextBoxTextProperty = DependencyProperty.Register("TextBoxText", typeof (String), typeof (CustomControl), new PropertyMetadata(default(String)));
public CustomControl()
{
InitializeComponent();
}
public String TextBoxText
{
get { return (String) GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
}
Thanks for any help on this. It really drives me crazy now.
EDIT:
I just came over two possible solutions:
Here the first (which works for me):
<!-- Give that child a name ... -->
<controls:ViewModel x:Name="viewModel"/>
<!-- ... and set it as ElementName -->
<controls:CustomControl TextBoxText="{Binding SampleText, ElementName=viewModel}"/>
The second one. This doesn't work in my case. I don't know why:
<controls:CustomControl TextBoxText="{Binding SampleText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:ViewModel}}}"/>
<!-- or -->
<controls:CustomControl TextBoxText="{Binding SampleText, RelativeSource={RelativeSource FindAncestor, AncestorType=controls:ViewModel}}"/>
I had a similar situation.
In my case, I fixed it with adding OnPropertyChanged in setter of property in ViewModel.

GoToState does not work for ControlTemplate in UserControl

I am totally lost and I would really appreciate your help on this.
My final goal is to create a user control that will contain two control templates. Square and a Circle. Based on a type the control will display one or the other. When the mouse enters the shape the Opacity will change to 0.2.
The first part works but the Opacity does not change. The event is triggered and a GoToState is called, but with no result. The Opacity stays 1.
My XAML:
<UserControl.Resources>
<ControlTemplate x:Key="TemplateSquare" TargetType="{x:Type local:KeyControl}">
<Canvas x:Name="MainCanvas" VerticalAlignment="Center" HorizontalAlignment="Center">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="CenterRectangle" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="0" To=".2"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="CenterRectangle" Fill="Red" Width="100" Height="100"></Rectangle>
</Canvas>
</ControlTemplate>
</UserControl.Resources>
<!-- IF I MOVE THE CANVAS HERE THE OPACITY CHANGES ON MOUSE OVER -->
Codebehind:
public partial class KeyControl : UserControl
{
private bool _isPressed = false;
private bool _isMouseOver = false;
public KeyControl()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(KeyControl_Loaded);
}
private void KeyControl_Loaded(object sender, RoutedEventArgs e)
{
//this will be set in the Type setter
this.Template = this.FindResource("TemplateSquare") as ControlTemplate;
this.MouseEnter += new MouseEventHandler(CorePart_MouseEnter);
this.MouseLeave += new MouseEventHandler(CorePart_MouseLeave);
GoToState(false);
}
private void GoToState(bool useTransitions)
{
if (_isPressed)
VisualStateManager.GoToState(this, "Pressed", useTransitions);
else if (_isMouseOver)
VisualStateManager.GoToState(this, "MouseOver", useTransitions);
else
VisualStateManager.GoToState(this, "Normal", useTransitions);
}
private void CorePart_MouseLeave(object sender, MouseEventArgs e)
{
_isMouseOver = false;
GoToState(true);
}
private void CorePart_MouseEnter(object sender, MouseEventArgs e)
{
_isMouseOver = true;
GoToState(true);
}
}
Can somebody please tell me where the problem could be?
Thank You
The UserControl makes it's Content the "root" element, which is used to locate the VisualStateGroups. If you use Reflector and look at UserControl.StateGroupsRoot, you'd see it looks like:
internal override FrameworkElement StateGroupsRoot {
get {
return (base.Content as FrameworkElement);
}
}
While FrameworkElement (and thus most other elements) use:
internal virtual FrameworkElement StateGroupsRoot {
get {
return (this._templateChild as FrameworkElement);
}
}
When setting the Template property, the Content property will still be null. When you move the Canvas to be below Resources, you are setting the Content property. So the visual state groups can be found in that case.
You can work around this by changing your control derive from ContentControl directly, and bypass UserControl. Simply change UserControl references to ContentControl in your XAML and code-behind.

Resources