SecurePage
which inherit from UserControl
. SecurePage
. Style
of SecurePage
a VisualStateGroup
with some VisualState
s. The problem is, that in the derived classes are none of these VisualState
s 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
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>
states = 0
VisualStateManager.GoToState(this, "Blink", false);
states = 0
VisualStateManager.GoToState(this, "Blink", false);
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
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!
After some poking around I've finally found the culprit - it's the UserControl
itself. More precisely - overridden StateGroupsRoot
property, which is used by the VisualStateManager.GoToState
method. Normally, it returns the root element of the control's template, but in case of UserControl
it returns the value of UserControl.Content
property. So what happens is that when you call GoToState
, states defined in your template are not taken into account.
There are at least two solutions to this problem.
First solution is to derive your base class (SecurePage
) from ContentControl
instead of UserControl
. The latter isn't that much different - it defaults Focusable
and IsTabStop
properties to false
, and HorizontanContentAlignment
and VerticalContentAlignment
to Stretch
. Also, apart from overriding mentioned StateGroupsRoot
property, it provides its own AutomationPeer
(a UserControlAutomationPeer
), but I don't think you need to worry about that.
The second solution is to use VisualStateManager.GoToElementState
on the template root instead. For example:
public class SecurePage : UserControl
{
//Your code here...
private FrameworkElement TemplateRoot { get; set; }
public override void OnApplyTemplate()
{
if (Template != null)
TemplateRoot = GetVisualChild(0) as FrameworkElement;
else
TemplateRoot = null;
}
public bool GoToVisualState(string name, bool useTransitions)
{
if (TemplateRoot is null)
return false;
return VisualStateManager.GoToElementState(TemplateRoot, name, useTransitions);
}
}
Calling VisualStateManager.GetVisualStateGroups
on 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 expect Grid.GetColumn
called on your control to return a value you set somewhere in your template.
Regarding calling GoToState
in your control's constructor - it's most likely not going to work, since your control is only being instantiated, and most likely its Templtate
is null (and remember you define visual states inside the template). It's better to move that logic to OnApplyTemplate
override.
1 Since VisualStateManager.VisualStateGroupsProperty
is read-only, by set I mean add items to the list returned by VisualStateManager.GetVisualStateGroups