设置 VisualState 的初始值
Setting the initial value of a VisualState
我对使用 VisualStateManager 的自定义控件有疑问。
状态之间的转换如我所料,但我不明白如何设置初始状态。
我做了完整的例子来说明问题。
此示例使用基于 ButtonBase 的自定义控件。
控件有一个 VisualState 组,有两个状态 "Checked" 和 "Unchecked"。
这是控件的C#代码。
using System.Windows;
using System.Windows.Controls.Primitives;
namespace VisualStateTest
{
[TemplateVisualStateAttribute(Name = "Checked", GroupName = "CheckStates")]
[TemplateVisualStateAttribute(Name = "Unchecked", GroupName = "CheckStates")]
public class CustomButton : ButtonBase
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register ( "IsChecked",
typeof(bool),
typeof(CustomButton),
new FrameworkPropertyMetadata ( false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnCheckedChanged ) ) ;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static void OnCheckedChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var button = d as CustomButton ;
if ((bool)e.NewValue)
{
VisualStateManager.GoToState(button, "Checked", true);
}
else
{
VisualStateManager.GoToState(button, "Unchecked", true);
}
}
}
}
设置 IsChecked 属性 后,控件模板在左侧和顶部显示阴影。
(我知道设计不好,但这不是图形设计的问题。)
这是控件模板:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VisualStateTest">
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border x:Name="outerborder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="topshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="leftshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="topshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="leftshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Cursor="Hand" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="lineargradient"
Grid.RowSpan="2" Grid.ColumnSpan="2"
Stroke="#7F000000"
StrokeThickness="0">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#20808080"/>
<GradientStop Color="#008A8A8A" Offset="0.5"/>
<GradientStop Color="#20000000" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter HorizontalAlignment="Center"
x:Name="contentPresenter"
Grid.RowSpan="2" Grid.ColumnSpan="2"
VerticalAlignment="Center" />
<Rectangle x:Name="topshadow" Fill="#40000000" Grid.Row="0" Grid.ColumnSpan="2" Opacity="0">
<Rectangle.Effect>
<BlurEffect Radius="3"/>
</Rectangle.Effect>
</Rectangle>
<Rectangle x:Name="leftshadow" Fill="#40000000" Grid.Row="1" Grid.Column="0" Opacity="0">
<Rectangle.Effect>
<BlurEffect Radius="3"/>
</Rectangle.Effect>
</Rectangle>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
对于此测试,我定义了一个具有两个布尔属性(Option1 和 Option2)的 ViewModel。
其中一个属性的初始值为 false,另一个为 true。
主要 window 有两个 CustomButton 控件,连接到两个选项属性,还有两个复选框连接到相同的属性。
这是视图模型的完整代码...
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace VisualStateTest
{
public class ViewModel : INotifyPropertyChanged
{
// Events for INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private bool _option1 = false ;
private bool _option2 = true ;
public ICommand Notify1Command { get; private set; }
public ICommand Notify2Command { get; private set; }
public ViewModel()
{
Notify1Command = new RelayCommand (new Action<object>(Execute_Notify1Command));
Notify2Command = new RelayCommand (new Action<object>(Execute_Notify2Command));
}
public bool Option1
{
get { return _option1 ; }
set
{
_option1 = value ;
NotifyPropertyChanged ( "Option1" ) ;
}
}
public bool Option2
{
get { return _option2 ; }
set
{
_option2 = value ;
NotifyPropertyChanged ( "Option2" ) ;
}
}
public void Execute_Notify1Command ( object value )
{
Option1 = !Option1 ;
}
public void Execute_Notify2Command ( object value )
{
Option2 = !Option2 ;
}
private void NotifyPropertyChanged ( String propertyName )
{
if ( this.PropertyChanged != null )
{
this.PropertyChanged ( this, new PropertyChangedEventArgs(propertyName) ) ;
}
}
}
}
和主要 window ...
<Window x:Class="VisualStateTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VisualStateTest"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<local:CustomButton Grid.Row="0" Grid.Column="0"
Command="{Binding Notify1Command}"
IsChecked="{Binding Option1, Mode=OneWay}"
Content="Option 1"
Margin="20"/>
<local:CustomButton Grid.Row="0" Grid.Column="1"
Command="{Binding Notify2Command}"
IsChecked="{Binding Option2, Mode=OneWay}"
Content="Option 2"
Margin="20"/>
<CheckBox Grid.Row="1" Grid.Column="0"
IsChecked="{Binding Option1}"
Content="Option 1"
Margin="20 5"/>
<CheckBox Grid.Row="1" Grid.Column="1"
IsChecked="{Binding Option2}"
Content="Option 2"
Margin="20 5"/>
</Grid>
</Window>
程序启动后,单击自定义按钮或复选框可切换选项并显示或隐藏阴影效果。
这是 'normal' 状态下的样子:
问题出在程序启动时。虽然Option2初始化为true,并且调用了函数VisualStateManager.GoToState,但是没有显示阴影效果
这是启动时的样子。
右侧的复选框表示选项 2 为真,但不存在阴影效果。
我确信我遗漏了一小块拼图。如果有帮助,我可以上传示例程序。
很抱歉,如果这太详细了。
我想我找到了答案。
我需要在自定义控件中覆盖函数 OnApplyTemplate()。我用以下函数扩展了 CustomButton class 的代码:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if ( IsChecked )
{
VisualStateManager.GoToState(this, "Checked", true);
}
else
{
VisualStateManager.GoToState(this, "Unchecked", true);
}
}
我通过阅读 Microsoft documentation 找到了此信息。关于 OnApplyTemplate 方法,它指出
This is the earliest that the FrameworkElement in the ControlTemplate is available to the control.
我对使用 VisualStateManager 的自定义控件有疑问。
状态之间的转换如我所料,但我不明白如何设置初始状态。
我做了完整的例子来说明问题。 此示例使用基于 ButtonBase 的自定义控件。
控件有一个 VisualState 组,有两个状态 "Checked" 和 "Unchecked"。 这是控件的C#代码。
using System.Windows;
using System.Windows.Controls.Primitives;
namespace VisualStateTest
{
[TemplateVisualStateAttribute(Name = "Checked", GroupName = "CheckStates")]
[TemplateVisualStateAttribute(Name = "Unchecked", GroupName = "CheckStates")]
public class CustomButton : ButtonBase
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register ( "IsChecked",
typeof(bool),
typeof(CustomButton),
new FrameworkPropertyMetadata ( false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnCheckedChanged ) ) ;
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static void OnCheckedChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e )
{
var button = d as CustomButton ;
if ((bool)e.NewValue)
{
VisualStateManager.GoToState(button, "Checked", true);
}
else
{
VisualStateManager.GoToState(button, "Unchecked", true);
}
}
}
}
设置 IsChecked 属性 后,控件模板在左侧和顶部显示阴影。
(我知道设计不好,但这不是图形设计的问题。)
这是控件模板:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VisualStateTest">
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border x:Name="outerborder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="topshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="leftshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1.0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="topshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="0:0:0"
Storyboard.TargetName="leftshadow"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Cursor="Hand" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="lineargradient"
Grid.RowSpan="2" Grid.ColumnSpan="2"
Stroke="#7F000000"
StrokeThickness="0">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#20808080"/>
<GradientStop Color="#008A8A8A" Offset="0.5"/>
<GradientStop Color="#20000000" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter HorizontalAlignment="Center"
x:Name="contentPresenter"
Grid.RowSpan="2" Grid.ColumnSpan="2"
VerticalAlignment="Center" />
<Rectangle x:Name="topshadow" Fill="#40000000" Grid.Row="0" Grid.ColumnSpan="2" Opacity="0">
<Rectangle.Effect>
<BlurEffect Radius="3"/>
</Rectangle.Effect>
</Rectangle>
<Rectangle x:Name="leftshadow" Fill="#40000000" Grid.Row="1" Grid.Column="0" Opacity="0">
<Rectangle.Effect>
<BlurEffect Radius="3"/>
</Rectangle.Effect>
</Rectangle>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
对于此测试,我定义了一个具有两个布尔属性(Option1 和 Option2)的 ViewModel。 其中一个属性的初始值为 false,另一个为 true。
主要 window 有两个 CustomButton 控件,连接到两个选项属性,还有两个复选框连接到相同的属性。
这是视图模型的完整代码...
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace VisualStateTest
{
public class ViewModel : INotifyPropertyChanged
{
// Events for INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private bool _option1 = false ;
private bool _option2 = true ;
public ICommand Notify1Command { get; private set; }
public ICommand Notify2Command { get; private set; }
public ViewModel()
{
Notify1Command = new RelayCommand (new Action<object>(Execute_Notify1Command));
Notify2Command = new RelayCommand (new Action<object>(Execute_Notify2Command));
}
public bool Option1
{
get { return _option1 ; }
set
{
_option1 = value ;
NotifyPropertyChanged ( "Option1" ) ;
}
}
public bool Option2
{
get { return _option2 ; }
set
{
_option2 = value ;
NotifyPropertyChanged ( "Option2" ) ;
}
}
public void Execute_Notify1Command ( object value )
{
Option1 = !Option1 ;
}
public void Execute_Notify2Command ( object value )
{
Option2 = !Option2 ;
}
private void NotifyPropertyChanged ( String propertyName )
{
if ( this.PropertyChanged != null )
{
this.PropertyChanged ( this, new PropertyChangedEventArgs(propertyName) ) ;
}
}
}
}
和主要 window ...
<Window x:Class="VisualStateTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VisualStateTest"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<local:CustomButton Grid.Row="0" Grid.Column="0"
Command="{Binding Notify1Command}"
IsChecked="{Binding Option1, Mode=OneWay}"
Content="Option 1"
Margin="20"/>
<local:CustomButton Grid.Row="0" Grid.Column="1"
Command="{Binding Notify2Command}"
IsChecked="{Binding Option2, Mode=OneWay}"
Content="Option 2"
Margin="20"/>
<CheckBox Grid.Row="1" Grid.Column="0"
IsChecked="{Binding Option1}"
Content="Option 1"
Margin="20 5"/>
<CheckBox Grid.Row="1" Grid.Column="1"
IsChecked="{Binding Option2}"
Content="Option 2"
Margin="20 5"/>
</Grid>
</Window>
程序启动后,单击自定义按钮或复选框可切换选项并显示或隐藏阴影效果。
这是 'normal' 状态下的样子:
问题出在程序启动时。虽然Option2初始化为true,并且调用了函数VisualStateManager.GoToState,但是没有显示阴影效果
这是启动时的样子。
右侧的复选框表示选项 2 为真,但不存在阴影效果。
我确信我遗漏了一小块拼图。如果有帮助,我可以上传示例程序。
很抱歉,如果这太详细了。
我想我找到了答案。
我需要在自定义控件中覆盖函数 OnApplyTemplate()。我用以下函数扩展了 CustomButton class 的代码:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if ( IsChecked )
{
VisualStateManager.GoToState(this, "Checked", true);
}
else
{
VisualStateManager.GoToState(this, "Unchecked", true);
}
}
我通过阅读 Microsoft documentation 找到了此信息。关于 OnApplyTemplate 方法,它指出
This is the earliest that the FrameworkElement in the ControlTemplate is available to the control.