我在使用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。
主窗口有两个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 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>
程序启动后,单击自定义按钮或复选框可切换选项并显示或隐藏阴影效果。
这就是“正常”状态下的样子:
问题出在程序启动时。虽然Option2初始化为true,并且调用了函数VisualStateManager.GoToState,但不显示阴影效果。
这就是它在启动时的样子。
右侧的复选框表示选项2为true,但阴影效果不存在。
我确信我错过了一小块难题。如果有帮助,我可以上传示例程序。
如果这个细节太多,我很抱歉。
答案 0 :(得分:0)
我认为我找到了答案。
我需要在自定义控件中覆盖OnApplyTemplate()函数。我使用以下函数扩展了CustomButton类的代码:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if ( IsChecked )
{
VisualStateManager.GoToState(this, "Checked", true);
}
else
{
VisualStateManager.GoToState(this, "Unchecked", true);
}
}
我通过阅读Microsoft documentation找到了这些信息。参考OnApplyTemplate方法,它说明了
这是ControlTemplate中的FrameworkElement可用于控件的最早版本。