- I have a base class
SecurePagewhich inherit fromUserControl. - Every 'page' inside of the app inherit from
SecurePage. - I want to define in the default
StyleofSecurePageaVisualStateGroupwith someVisualStates.
The problem is, that in the derived classes are none of these VisualStates available.
var states = VisualStateManager.GetVisualStateGroups(this);
Returns an empty list.
If I copy my XAML VisualState definition and paste it into my DerivadedFooSecurePage, I can easily go to the state:
VisualStateManager.GoToState(this, "Blink", false);
Same problem as described here: VisualState in abstract control
Some more Details
SecurePage
[TemplateVisualState(GroupName = "State", Name = "Normal")]
[TemplateVisualState(GroupName = "State", Name = "Blink")]
public class SecurePage: UserControl
{
public SecurePage()
{
DefaultStyleKey = typeof(HtSecurePage);
}
}
<Style TargetType="basic:SecurePage">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="basic:SecurePage">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Signals">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Blink">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FF3AFF00">
<EasingColorKeyFrame.EasingFunction>
<BounceEase EasingMode="EaseIn" Bounciness="3" Bounces="4"/>
</EasingColorKeyFrame.EasingFunction>
</EasingColorKeyFrame>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter Content="{TemplateBinding Content}"/>
<Border
x:Name="border"
BorderThickness="5"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
InfoPage
Info.xaml.cs
namespace Views.General
{
public partial class Info
{
public Info()
{
InitializeComponent();
}
}
}
Info.xaml
<basic:SecurePage
x:Class="Views.General.Info"
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:basic="clr-namespace:Foo.PlcFramework.Controls.Basic;assembly=Foo"
FontSize="14">
<Grid>
<TextBlock Text="HelloWorld"/>
</Grid>
</basic:SecurePage>
Live Debugging
states = 0- Nothing happens after calling
VisualStateManager.GoToState(this, "Blink", false);
states = 0- Nothing happens after calling
VisualStateManager.GoToState(this, "Blink", false);
Copy the VisualState into the derivaded class
namespace Views.General
{
[TemplateVisualState(GroupName = "State", Name = "Normal")]
[TemplateVisualState(GroupName = "State", Name = "Blink")]
public partial class Info
{
public Info()
{
InitializeComponent();
var states = VisualStateManager.GetVisualStateGroups(this);
VisualStateManager.GoToState(this, "Blink", false);
}
}
}
<basic:SecurePage
x:Class="Views.General.Info"
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:basic="clr-namespace:Foo.PlcFramework.Controls.Basic;assembly=Foo"
FontSize="14">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Signals">
<VisualState x:Name="Normal"/>
<VisualState x:Name="Blink">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:0.4" Value="#FF3AFF00">
<EasingColorKeyFrame.EasingFunction>
<BounceEase EasingMode="EaseIn" Bounciness="3" Bounces="4"/>
</EasingColorKeyFrame.EasingFunction>
</EasingColorKeyFrame>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock Text="HelloWorld"/>
<Border
x:Name="border"
BorderThickness="5"
BorderBrush="Transparent"
IsHitTestVisible="False"/>
</Grid>
</basic:SecurePage >
states = 0- After calling
VisualStateManager.GoToState(this, "Blink", false);the state is changed!!
I just want to define the state in the XAML Style definition of SecurePage and go to the state in any derivaded class!



Diagnosis
After some poking around I've finally found the culprit - it's the
UserControlitself. More precisely - overriddenStateGroupsRootproperty, which is used by theVisualStateManager.GoToStatemethod. Normally, it returns the root element of the control's template, but in case ofUserControlit returns the value ofUserControl.Contentproperty. So what happens is that when you callGoToState, states defined in your template are not taken into account.Solution
There are at least two solutions to this problem.
First solution is to derive your base class (
SecurePage) fromContentControlinstead ofUserControl. The latter isn't that much different - it defaultsFocusableandIsTabStopproperties tofalse, andHorizontanContentAlignmentandVerticalContentAlignmenttoStretch. Also, apart from overriding mentionedStateGroupsRootproperty, it provides its ownAutomationPeer(aUserControlAutomationPeer), but I don't think you need to worry about that.The second solution is to use
VisualStateManager.GoToElementStateon the template root instead. For example:Other considerations
Calling
VisualStateManager.GetVisualStateGroupson your control yields an empty list because it's just an ordinary attached dependency property accessor, and you didn't set1 that property on your control. To get hold of the groups you've defined in the template, you should call it passing the template root as argument. By the same principle, you don't expectGrid.GetColumncalled on your control to return a value you set somewhere in your template.Regarding calling
GoToStatein your control's constructor - it's most likely not going to work, since your control is only being instantiated, and most likely itsTempltateis null (and remember you define visual states inside the template). It's better to move that logic toOnApplyTemplateoverride.1 Since
VisualStateManager.VisualStateGroupsPropertyis read-only, by set I mean add items to the list returned byVisualStateManager.GetVisualStateGroups