Change stroke and fill on custom button WPF
我在 C# 编写的 WPF 应用程序中使用多边形和矩形创建了一个用于媒体播放器控件的模板。我为按钮设置了三个"状态"。
1 2 3 | Idle = stroke=Black; Fill=Black MouseOver = stroke=White; Fill=Black MouseDown/Disabled = Stroke=black; fill=White |
我有前两个工作,但我不能得到最后一个。到目前为止,我有以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <Button Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center" Command="{Binding PlayCommand}"> <Button.Template> <ControlTemplate TargetType="Button"> <Canvas Height="24" Width="18"> <Polygon x:Name="play" Points="2,0 18,9 2,18" Fill="Black" Canvas.Top="2"/> </Canvas> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="play" Property="Stroke" Value="White"/> </Trigger> <Trigger Property="IsMouseOver" Value="False"> <Setter TargetName="play" Property="Stroke" Value="Black"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Template> </Button> |
播放命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class PlayCommand : ICommand { private MainWindowViewModel _viewModel; public PlayCommand(MainWindowViewModel viewModel) { _viewModel = viewModel; } public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return !_viewModel.IsPlaying; } public void Execute(object parameter) { _viewModel.Play(); // sets IsPlaying to true } } |
ViewModel 实现 INotifyPropertyChanged
1 2 3 4 5 6 7 8 9 10 11 12 | public bool IsPlaying { get {return _isPlaying; } private set { if (value != _isPlaying) { _isPlaying = value; OnPropertyChanged(new PropertyChangedEventArgs("IsPlaying")); } } } |
当我尝试构建查看 IsEnabled 以设置笔触和填充的触发器时,即使按钮被禁用,它们似乎也不会被触发。有什么建议吗?提前致谢。
编辑:
App.xaml.cs
1 2 3 4 5 6 |
ViewModel 构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 | public MainWindowViewModel(Configuration config) { this.config = config; _player = new MediaPlayerMock(); _player.StatusChanged += _player_StatusChanged; PlayCommand = new PlayCommand(this); StopCommand = new StopCommand(this); } public ICommand PlayCommand { get; set; } public ICommand StopCommand { get; set; } |
VisualState 管理器也可以在这种情况下使用。视觉状态为您封装了所有这些,并让您可以根据状态而不是转换来更改控件。这是一个(可能很重要的)区别。
TL;DR 在底部顺便说一句。
当使用数据触发器或一般触发器时,您将受益于使用您想要触发不同样式的任何属性和事件。但您只能访问这些转换。
相反,状态对状态起作用。区别很简单。转换没有关于状态的信息。如果将鼠标悬停在只读文本框控件上会发生什么?是的,它开始了悬停更改,尽管悬停在这里不适用,因为文本框不可编辑。各state可以照顾到这一点,因为它们只有在满足所有条件时才会改变。触发器会在红色虚线上发生变化,而状态不会。
除了获得一致的状态而不是转换之外,您还可以获得更多好处。您还可以访问控件的焦点状态或验证状态,这是依赖属性提供的一种机制。
要找出您想要的,您可以查看您想要更改的内容和时间。如果您希望将鼠标悬停在控件上以产生与之前未单击而悬停不同的结果,那么您正在寻找过渡。如果您只想对控件的特定结果状态进行可视化编码,请尽可能使用 VisualStateManager。
TL;DR:触发器和视觉状态不同,行为也不同。您可能想评估您需要哪一个。视觉状态仅在模板中定义,这可能是使用它们的缺点。您还需要使用情节提要,但持续时间为零,它们也会立即应用。可以在此处找到按钮的所有状态列表。例如,可以按如下方式创建平面按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <ControlTemplate TargetType="Button"> <Grid Background="{TemplateBinding Background}" TextBlock.Foreground="{TemplateBinding Foreground}"> <Rectangle Fill="White" Opacity="0." x:Name="Overlay" /> <Rectangle Fill="Gray" Opacity="0." x:Name="OverlayDark" /> <ContentPresenter Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" /> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal"> <VisualState.Storyboard> <Storyboard> <DoubleAnimation To="0.0" Duration="0:0:0.1" Storyboard.TargetName="Overlay" Storyboard.TargetProperty="Opacity" /> </Storyboard> </VisualState.Storyboard> </VisualState> <VisualState Name="Pressed"> <Storyboard> <DoubleAnimation To="0.5" Duration="0:0:0.1" Storyboard.TargetName="OverlayDark" Storyboard.TargetProperty="Opacity" /> </Storyboard> </VisualState> <VisualState Name="MouseOver"> <VisualState.Storyboard> <Storyboard> <DoubleAnimation To="0.5" Duration="0:0:0.1" Storyboard.TargetName="Overlay" Storyboard.TargetProperty="Opacity" /> </Storyboard> </VisualState.Storyboard> </VisualState> <VisualState Name="Disabled"> <Storyboard> <DoubleAnimation To="1" Duration="0:0:0.1" Storyboard.TargetName="OverlayDark" Storyboard.TargetProperty="Opacity" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> |
您应该在视图模型的
1 2 3 4 5 6 7 8 9 10 11 | public class PlayCommand : ICommand { ... public event EventHandler CanExecuteChanged; public void Execute(object parameter) { _viewModel.Play(); CanExecuteChanged?.Invoke(this, EventArgs.Empty); } } |
或者,您可以在
1 2 3 | <DataTrigger Binding="{Binding IsPlaying}" Value="False"> <Setter ... /> </DataTrigger> |