How to draw a shape or lines inside a WPF button, the size of which depends on the control, without forcing the button to expand forever?

I had a quick google and I quickly looked through StackOverflow but couldn't find anyone else who ran into this problem.

I want to create a close button with a little X in it. The close button will do things such as fading in and out when you hover over an item containing it, change color when you hover, and all the usual fun of WPF. The bottom line, it seems like it's not that difficult, but I ran into one of the strangest problems ever before I got to this stage.

Here is 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 as a test, for example:

 <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, where everything goes terribly wrong. When you launch the application, the button will expand indefinitely, one pixel at a time, horizontally and vertically, until it reaches the height of the window.

Now I can understand why this happens: the lines actually go one unit beyond the width and height of the grid, causing the grid to expand by one, then there is relaying, data binding leads to redrawing the lines, declaring infinity.

So, in order to try to deal with this, I decided to put it in a grid, but then, no matter what I do with HorizontalAlignment and VerticalAlignment, I get both a canvas and a grid having zero width and height, which means I don't get your cross, which is very annoying. If I snap to the ActualWidth and ActualHeight properties of the button, I just end up with X having the upper left corner centered on the center of the button.

Does anyone have any ideas what I can do to fix this? I would be extremely grateful for any pointers - WPF is still a pretty new beast to me.

Thanks!

+7
button wpf xaml size
source share
3 answers

You do not need to resort to bindings to make a layout. As you have seen, bindings and layout systems do not work in concert (I think data binding takes precedence over layout, but in your case, the layout calls another data binding)

You should be able to get a pretty pretty, extensible X with just Path:

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

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

+15
source share

Instead of binding, I suggest using the Stretch and Margin or Padding properties, for example 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 is the minimum height and width. If you cannot just use height and width, I would suggest 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> 
+2
source share

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

Here is 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!

0
source share

All Articles