laptou
laptou

Reputation: 6981

Custom Caption Buttons in WPF

I am creating a custom window style in WPF. So far, I have been able to accomplish something that almost works, in pure XAML.

<ControlTemplate TargetType="{x:Type Window}">
    <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
        Background="{TemplateBinding Background}">
        <DockPanel LastChildFill="True">
        <Border Height="40" Padding="5" DockPanel.Dock="Top" BorderBrush="#7FA0A0A0" 
            BorderThickness="0,0,0,1">
            <Grid WindowChrome.IsHitTestVisibleInChrome="True">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
                <Image Source="Resources/Logo/f-dark.png" />
                <TextBlock Margin="0,0,0,0" Text="{TemplateBinding Title}" VerticalAlignment="Center" 
                FontSize="16" Foreground="#AF000000"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
                    WindowChrome.IsHitTestVisibleInChrome="True" Background="Transparent">
                <Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
                    Command="{x:Static SystemCommands.MinimizeWindowCommand}"
                    Style="{StaticResource LinkButton}"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    IsEnabled="True">
                <TextBlock Style="{StaticResource Icon}" FontSize="18">&#xE15B;</TextBlock>
                </Button>
                <Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
                    Command="{x:Static SystemCommands.RestoreWindowCommand}"
                    Style="{StaticResource LinkButton}"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    IsEnabled="True">
                <TextBlock Style="{StaticResource Icon}" FontSize="18">&#xE5D0;</TextBlock>
                </Button>
                <Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
                    Command="{x:Static SystemCommands.CloseWindowCommand}"
                    Style="{StaticResource LinkButton}"
                    WindowChrome.IsHitTestVisibleInChrome="True"
                    IsEnabled="True">
                <TextBlock Style="{StaticResource Icon}" FontSize="18">&#xE5CD;</TextBlock>
                </Button>
            </StackPanel>
            </Grid>

        </Border>
        <Border>
            <ContentPresenter/>
        </Border>

        </DockPanel>
    </Border>
</ControlTemplate>

However, the in the window chrome buttons do not highlight or change the cursor (and buttons with the same style behave as intended). Clicking on them has no effect. Why is this? How do I get the buttons to be interactive?

When I first open the window, I get something like this: an example window

I would also like to be able to fix the black border, but my main problem is the fact that the caption buttons are noninteractive.

Upvotes: 0

Views: 861

Answers (1)

laptou
laptou

Reputation: 6981

I have solved both problems. It turns out that the SystemCommands do not have implementations, and instead are just RoutedCommands. (why did they do that?)

So, I made an attached property that sets the CommandBindings for each window that I apply this style to.

public class Helper
{
    public static bool GetUseWindowCommandBindings(DependencyObject obj)
    {
        return (bool)obj.GetValue(UseWindowCommandBindingsProperty);
    }

    public static void SetUseWindowCommandBindings(DependencyObject obj, bool value)
    {
        obj.SetValue(UseWindowCommandBindingsProperty, value);
    }

    public static readonly DependencyProperty UseWindowCommandBindingsProperty =
        DependencyProperty.RegisterAttached("UseWindowCommandBindings", typeof(bool), typeof(Helper), new PropertyMetadata(false, UseWindowCommandBindingsChanged));

    private static void UseWindowCommandBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs dpce)
    {
        if (d is Window w && dpce.NewValue is bool b && b)
        {
            w.CommandBindings.Add(
                new CommandBinding(
                    SystemCommands.MinimizeWindowCommand,
                    (s, e) => SystemCommands.MinimizeWindow(w),
                    (s, e) => e.CanExecute = true
                    ));
            w.CommandBindings.Add(
                new CommandBinding(
                    SystemCommands.RestoreWindowCommand,
                    (s, e) => SystemCommands.RestoreWindow(w),
                    (s, e) => e.CanExecute = true
                    ));
            w.CommandBindings.Add(
                new CommandBinding(
                    SystemCommands.MaximizeWindowCommand,
                    (s, e) => SystemCommands.MaximizeWindow(w),
                    (s, e) => e.CanExecute = true
                    ));
            w.CommandBindings.Add(
                new CommandBinding(
                    SystemCommands.CloseWindowCommand,
                    (s, e) => SystemCommands.CloseWindow(w),
                    (s, e) => e.CanExecute = true
                    ));
        }
    }
}

I had to put the attachment of the CommandBindings in the property-change handler since WPF calls SetValue() directly. Now, in my style, I added the line:

<Setter Property="view:Helper.UseWindowCommandBindings" Value="True" />

Now the buttons work as expected. To fix the black border, I set the width of the outermost Border in my template to {TemplateBinding Width}. But this creates the problem of having a massive, all-around black border if I maximize the window: enter image description here

Upvotes: 2

Related Questions