【问题标题】:How to Create a Wrapping Button in WPF如何在 WPF 中创建环绕按钮
【发布时间】:2011-07-28 20:49:35
【问题描述】:

要求:

  • 必须支持 MinWidth 属性
  • 只能在一行或两行上显示文本。不能换行到 3 个或更多。
  • 在尊重 MinWidth 的同时应使按钮尽可能小
  • 文本只能在空格上换行
  • 无法指定宽度。按钮应保持水平增长。

这是我希望按钮的样子(请忽略样式,包装是重要部分)

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

【问题讨论】:

    标签: wpf xaml layout button


    【解决方案1】:

    我试图通过编辑Button 的默认模板来实现这一点,主要是添加一个包装TextBlock 而不是默认的ContentPresenter 并在转换器中计算其Width。这种方法虽然需要转换器中的大量数据,但可能有更简单(更好:)的方法可以做到这一点,但它似乎仍然有效。它需要对 PresentationFramework.Aero 的引用,因为默认模板中的 ButtonChrome

    就这样用吧

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


    StackPanel 中包含 3 个 WrappingButtons 的屏幕截图示例

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 您可以将其 MaxTextWidth 设置为按钮宽度,然后再次检查 ft.Height。如果它是原始高度的 2 倍以上,则您有超过 2 条线,并希望将 MaxHeight 设置为原始高度的 2 倍左右。
      • 我会试一试,让你知道它是如何工作的。感谢您的想法!
      • 我已经用它做了几乎完全一样的事情,所以如果它不适合你,请告诉我。我只需要在 cmets 上发帖,因为今天我在发短文时会抛出错误。
      • 我有代码可以将字符串正确拆分成行,但是您如何处理显示新拆分的行?
      • 我写了一个简单的自定义面板。在 MeasureOverride 中,我使用可用的大小来决定如何显示线条(单行或双行)。在 ArrangeOverride 中,我只是按照它们在 MeasureOverride 中的布局(修改 FormattedText)放置它们,然后将文本居中。
      【解决方案3】:

      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>
      

      【讨论】:

        【解决方案4】:

        使用 RichTextBox 作为 Button 的内容:

                    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;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-10-04
          • 2011-10-08
          • 1970-01-01
          • 2017-05-05
          • 1970-01-01
          • 2011-11-06
          • 2012-09-20
          相关资源
          最近更新 更多