How to draw shape or lines within a WPF button tha

2019-04-24 18:41发布

问题:

I had a quick Google, and a quick look around StackOverflow, but couldn't find anyone else who'd run into this problem.

I want to create a close button with a little X in it. The close button will do things like fade in and out when you're hovering over the item containing it, change colour when you mouse over, and all the usual WPF loveliness. Bottom line, it doesn't seem like it should be that difficult, but I'm running into one of the weirdest issues ever before I've even got to this stage.

Here's my XAML style for the button:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="TabCloseButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Content">
            <Setter.Value>
                <Grid>
                    <Line Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
                            X1="0"
                            Y1="0"
                            X2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth, Mode=OneWay}"
                            Y2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualHeight, Mode=OneWay}"/>
                    <Line Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
                            X1="0"
                            Y1="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualHeight, Mode=OneWay}"
                            X2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth, Mode=OneWay}"
                            Y2="0"/>
                </Grid>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

And I create my button, just as a test, like this:

<Window x:Class="WpfTestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="124" Width="569">
    <Grid Background="#2b3c59">
        <StackPanel Orientation="Horizontal">

            <!-- Other controls removed for clarity -->

            <Button Style="{DynamicResource TabCloseButtonStyle}"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Bottom"
                    Padding="2"
                    Margin="0, 10, 0, 0"
                    MinWidth="50"
                    MinHeight="50"></Button>
        </StackPanel>
    </Grid>
</Window>

And here's where it all goes horribly wrong. When you run the application the button will expand infinitely, one pixel at a time, horizontally, and vertically until it hits the height of the window.

Now I can understand why this is happening: the Lines actually go one unit beyond the width and height of the Grid causing the Grid to expand by one unit, then there's a relayout, the data binding causes the lines to redraw, ad infinitum.

So, to try and deal with this I decided to put a in the grid but then, no matter what I do with HorizontalAlignment and VerticalAlignment, I end up with both the Canvas and the Grid having zero width and height, which means I don't get my cross, which is massively irritating. If I bind to the ActualWidth and ActualHeight properties of the button I just end up with an X that has the top-left corner centred on the centre of the button.

Does anybody have any idea at all what I can do to fix this? I'd be extremely grateful for any pointers - WPF is still a rather new beast to me.

Thanks!

回答1:

You shouldn't have to resort to bindings just to do layout. As you've seen, the bindings and the layout systems don't work in concert (I think databinding gets priority over layout, but in your case, layout causes another databinding)

You should be able get quite a nice looking, stretchable X with a just a Path:

<Path Data="M0,0 L1,1 M0,1 L1,0" Stretch="Uniform" Stroke="Red" />

If you want it to scale with the button size instead of stretch (scale affects apparent stroke width), then just use a ViewBox



回答2:

Instead of binding, I'd suggest using the Stretch and Margin or Padding properties, like the following

<Grid Margin="2">
    <Line X1="0" Y1="0" Y2="1" X2="1" Stroke="Black" Stretch="Fill" />
    <Line Y1="1" X2="1" Stroke="Black" Stretch="Fill" />
</Grid>

Update

So the problem with your example seems to be the min height & width. If you can't just use height & width, I'd suggest something like the following

<Grid MinHeight="{TemplateBinding MinHeight}" MinWidth="{TemplateBinding MinWidth}">
    <Line X1="0" Y1="0" Y2="1" X2="1" Stroke="Black" Stretch="Fill" />
    <Line Y1="1" X2="1" Stroke="Black" Stretch="Fill" />
</Grid>


回答3:

Thanks everyone. Rob's solution with the Viewbox turned out to be the answer. The button behaves perfectly, even if I remove the MidWidth and MinHeight attributes from the Button declaration in MainWindow.xaml.

Here's my modified style:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="TabCloseButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Content">
        <Setter.Value>
            <Grid>
                <Viewbox>
                    <Path Data="M0,0 L10,10 M0,10 L10,0"
                          Stretch="Uniform"
                          Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground, Mode=OneWay}" />
                </Viewbox>
            </Grid>
        </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Thanks again for all the suggestions!