关于c#:自定义按钮WPF上的更改描边和填充

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
private void App_OnStartup(object sender, StartupEventArgs e)
    {
        Configuration config = new Configuration();
        MainWindow main = new MainWindow(config);
        main.Show();
    }

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可以照顾到这一点,因为它们只有在满足所有条件时才会改变。触发器会在红色虚线上发生变化,而状态不会。

Example

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>


您应该在视图模型的 IsPlaying 属性更改时触发 CanExecuteChanged 事件:

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);
    }
}

或者,您可以在 IsPlaying 上使用 DataTrigger 而不是在 IsEnabled 上使用 Trigger:

1
2
3
<DataTrigger Binding="{Binding IsPlaying}" Value="False">
    <Setter ... />                    
</DataTrigger>