如何在WPF中创建一个包装按钮

5

要求:

  • 必须支持MinWidth属性
  • 只能显示一到两行文本,不能换行到三行或更多
  • 应该尽量使按钮变小,同时遵守MinWidth
  • 文本只能在空格处换行
  • 不能指定宽度。按钮应该在水平方向上继续增长。

这是我想让按钮看起来像什么(请忽略样式,请注意换行是重要部分)

enter image description here

我正在寻找有关如何动态换行的想法。

4个回答

2
我尝试通过编辑默认的Button模板来实现这一点,主要是添加一个包装的TextBlock而不是默认的ContentPresenter,并在转换器中计算其Width。虽然这种方法需要在转换器中处理很多数据,但可能有更简单(和更好的 :))的方法来完成此操作,但无论如何似乎都能正常工作。由于默认模板中的ButtonChrome,它需要引用PresentationFramework.Aero

只需像这样使用它

<Button Style="{StaticResource WrappingButton}"
        MinWidth="100"
        Content="This button has some long text">
</Button>

在此输入图片描述
一个包含3个WrappingButtonsStackPanel的截图示例

WrappingButton 样式

<Style x:Key="WrappingButton" TargetType="{x:Type Button}"
       xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
    <Setter Property="HorizontalAlignment" Value="Left"/>
    <Setter Property="Width" Value="Auto"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <ControlTemplate.Resources>
                    <local:WrappingButtonWidthConverter x:Key="WrappingButtonWidthConverter"/>
                </ControlTemplate.Resources>
                <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding IsDefaulted}" SnapsToDevicePixels="true">
                    <TextBlock VerticalAlignment="Center"
                               FontSize="{TemplateBinding FontSize}"
                               FontFamily="{TemplateBinding FontFamily}"
                               FontStyle="{TemplateBinding FontStyle}"
                               FontWeight="{TemplateBinding FontWeight}"
                               FontStretch="{TemplateBinding FontStretch}"
                               LineStackingStrategy="BlockLineHeight"
                               TextWrapping="Wrap"
                               TextTrimming="WordEllipsis"
                               Text="{Binding RelativeSource={RelativeSource TemplatedParent},
                                              Path=Content}">
                        <TextBlock.Width>
                            <MultiBinding Converter="{StaticResource WrappingButtonWidthConverter}">
                                <Binding RelativeSource="{RelativeSource Self}" Path="Text"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontFamily"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStyle"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontWeight"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStretch"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontSize"/>
                                <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MinWidth"/>
                            </MultiBinding>
                        </TextBlock.Width>
                    </TextBlock>
                </Microsoft_Windows_Themes:ButtonChrome>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsKeyboardFocused" Value="true">
                        <Setter Property="RenderDefaulted" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="ToggleButton.IsChecked" Value="true">
                        <Setter Property="RenderPressed" TargetName="Chrome" Value="true"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="#ADADAD"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

WrappingButtonWidthConverter

public class WrappingButtonWidthConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string text = values[0].ToString();
        FontFamily fontFamily = values[1] as FontFamily;
        FontStyle fontStyle = (FontStyle)values[2];
        FontWeight fontWeight = (FontWeight)values[3];
        FontStretch fontStretch = (FontStretch)values[4];
        double fontSize = (double)values[5];
        double minWidth = (double)values[6];

        string[] words = text.Split(new char[] {' '});
        double widthSum = 0.0;
        List<double> wordWidths = GetWordWidths(words, fontFamily, fontStyle, fontWeight, fontStretch, fontSize, out widthSum);

        double width = 0.0;
        for (int i = 0; width < (widthSum / 2.0) && i < wordWidths.Count; i++)
        {
            width += wordWidths[i];
        }

        return minWidth > 0.0 ? Math.Max(minWidth, width) : width;
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    private List<double> GetWordWidths(string[] words,
                                       FontFamily fontFamily,
                                       FontStyle fontStyle,
                                       FontWeight fontWeight,
                                       FontStretch fontStretch,
                                       double fontSize,
                                       out double widthSum)
    {
        List<double> wordWidths = new List<double>();
        widthSum = 0.0;
        foreach (string word in words)
        {
            Typeface myTypeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
            FormattedText ft = new FormattedText(word + " ",
                                                 CultureInfo.CurrentCulture,
                                                 FlowDirection.LeftToRight,
                                                 myTypeface,
                                                 fontSize,
                                                 Brushes.Black);
            wordWidths.Add(ft.WidthIncludingTrailingWhitespace);
            widthSum += ft.WidthIncludingTrailingWhitespace;
        }
        return wordWidths;
    }
}

1

由于您将文本限制为两行,我的建议是编写自定义面板作为按钮内容,将指定的文本、字体等转换为WPF格式化文本对象。然后可以在MeasureOverride和ArrangeOverride中测量它,并决定如何布局和显示。格式化文本甚至有一个参数来显示...缩写,如果文本不适合的话。为了保持两行,您需要先创建它,然后检查其高度,以查看单行的高度是多少。(需要在注释中添加其余部分,因为StackOverflow会抛出错误)。


然后,您可以将其MaxTextWidth设置为按钮宽度,然后再次检查ft.Height。如果它超过原始高度的2倍,则有超过2行,并且希望将MaxHeight设置为原始高度的约2倍。 - Ed Bayiates
我会尝试一下并告诉你它的效果如何。谢谢你的想法! - Shaun Bowe
我已经用它做了几乎完全相同的事情,所以如果它对你不起作用,请告诉我。今天我只能在评论中发布,因为 SO 在我发表除短帖子以外的任何内容时都会出错。 - Ed Bayiates
我已经成功编写了代码,将字符串正确地拆分成行,但是您是如何处理显示新拆分的行的? - Shaun Bowe
我编写了一个简单的自定义面板。在MeasureOverride中,我使用可用的大小来决定如何显示线条(单线或双线)。在ArrangeOverride中,我只是按照在MeasureOverride中布局的方式放置它们(这会修改FormattedText),然后将文本居中。 - Ed Bayiates

1

http://wpf.2000things.com/2011/07/11/339-wrapping-a-buttons-text-content-to-multiple-lines/

这对我很有帮助。还有这个:

 <Window.Resources>
        <local:Questions x:Key="theQs"/>
        <Style x:Key="WrappingButton" TargetType="{x:Type Button}">
            <Setter Property="FontSize" Value="10">

            </Setter>
        </Style>
    </Window.Resources>

和这个:

<Button Name="btnDef4" Grid.Row="3" Style="{StaticResource WrappingButton}" >
                <TextBlock Text="{Binding ElementName=LVQuestions, Path=SelectedItem.TheDefs[3].Deftext}"
                TextWrapping="Wrap"/>
            </Button>

0

将RichTextBox用作您的按钮内容:

            RichTextBox rtb = new RichTextBox();
            rtb.IsReadOnly = true;
            rtb.Focusable = false;
            rtb.BorderThickness = new Thickness(0);
            rtb.Background = Brushes.Transparent;
            rtb.AppendText("This button has some long text.");
            myButton.Content = rtb;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接