WPF 文本框选择垂直居中或对齐

WPF textbox selection vertical centering or alignment

我已经开始学习 WPF,我的首要任务是探索最基本的控件,例如文本框。它引起了我的注意,因为在 WPF 中,一切都更加可定制和强大,但它引起了我的注意,默认文本框的设计与 Winforms 的不同,获得了一个相当丑陋的设计。

在下图中,我们看到一个包含所选文本的文本框。一个 Winforms 文本框和一个 WPF 文本框。

如您所见,在 Winforms 中,选择的上部和下部突出显示 space(以紫色突出显示)分布均匀,而在 WPF 中,上部要大得多,外观相当“丑陋”选择文本时显示到文本框。

编辑:似乎有些用户没有正确理解问题。所以我去添加WPF文本框的xaml 接下来是:

<TextBox HorizontalAlignment="Left" Margin="183,172,0,0" Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>

当我将 属性 更改为 VerticalAlignment="Center" 时问题已解决,直到您更改 FontSize 后问题再次出现。问题是我的字体大小是16px.

我想知道有没有什么方法可以解决这个视觉错位,因为我真的不明白为什么他们会留下这么多space文本选择的顶部。

可能我应该触及字体字形顶部和底部 space 之间差异的原因。简而言之,顶部space保留用于在字母字符上方绘制符号(分音符)。

以下代码生成如下所示的文本框。

<Grid Margin="10">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.ColumnSpan="2"
             Text="0ay/AÁÊÑÖ 0ay/AÁÊÑÖ"
             FontFamily="Verdana" FontSize="36"
             Padding="3"/>

    <Rectangle Grid.Column="0"
               Stroke="DarkViolet" StrokeThickness="1" StrokeDashArray="1 3"
               VerticalAlignment="Center" HorizontalAlignment="Stretch"/>
    <Rectangle Grid.Column="1"
               Stroke="Red" StrokeThickness="1" StrokeDashArray="1 3"
               VerticalAlignment="Center" Margin="0,3,0,0" HorizontalAlignment="Stretch"/>
</Grid>

蓝色高亮区域为字体区域,紫色虚线为字体实际垂直居中。如果没有符号,您可能会认为红色虚线是垂直中心,但事实并非如此。字体字形与底部略微对齐,以容纳 space 个符号。

设置VerticalContentAlignment="Center"可以调整文本框内字体的垂直对齐方式,但不能更改字体本身内字体字形的位置。因此,我认为使字体字形(符号除外)真正显示在TextBox垂直中心的唯一方法是将字体移动任意长度以抵消字体内部的差异。

请注意,字体内部的差异可能因每种字体而异,如果顶部 space 的高度被切掉,并且如果键入包含这些符号的文本,则可以隐藏这些符号。

也就是说,您可以通过设置 Padding 属性 来调整内部填充,如下所示。

<TextBox Padding="0,-2,0,0" ClipToBounds="True" ... />

或者如果您不介意影响多行之间的间距,您可以通过设置内部文本的行高来降低 font griph 和 padding 之间的 space。

<TextBox Padding="2" FontSize="16"
         TextBlock.LineHeight="18" TextBlock.LineStackingStrategy="BlockLineHeight" ... />

编辑: 添加解释和备选建议以回应 OP 的评论。

要使内容(在本例中为文本)居中,您可以使用 VerticalContentAlignment

<TextBox VerticalContentAlignment="Center"... />

如果您设置了 Height 属性,请尝试将其删除。这会以某种方式影响对齐。如果改变 FrontSize,会出现类似的问题。

为了找出 TextBoxText-属性 是如何对齐的,我写了一个小应用程序。在此,可以使用 Controls 更改各种属性,您可以立即看到结果。

应用程序的源代码在答案的底部。

正如 @emoacht 已经描述的那样,在计算 space 要求时会考虑字体的所有字形。 文本对齐的参考点在右上角

为了使特定文本居中,您可以调整 Padding-属性HeightFontSize-属性 使文本居中。

请注意:我不会采用这种方法。由于所有这些属性都相互影响,因此会出现不一致的行为。用户输入您没有考虑过的字符或字符串就足够了,并且整个对齐都消失了。我认为更重要的是完整和正确地呈现文本。许多用户甚至不会注意到文本不在中间。

TextBox 的源代码 - TestApp:

<Window x:Class="TextBoxTestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TextBoxTestApp"
        xmlns:clr="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="TextBox - TestApp -- MK-NEUKO" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="testTextBoxStyle" TargetType="{x:Type TextBox}">
            <Setter Property="OverridesDefaultStyle" Value="True"/>
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBox}">
                        <Border BorderBrush="Black"
                                BorderThickness="1"
                                CornerRadius="2">
                            <Border.Background>
                                <LinearGradientBrush StartPoint="0, 0" EndPoint="0, 0.25"
                                                     SpreadMethod="Repeat">
                                    <GradientStop Color="White"
                                                  Offset="0"/>
                                    <GradientStop Color="Gray"
                                                  Offset="1"/>
                                </LinearGradientBrush>
                            </Border.Background>
                            <ScrollViewer x:Name="PART_ContentHost"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <WrapPanel Grid.Row="0"
                    
                    Orientation="Horizontal"
                    Margin="5">
            <GroupBox Header="Style Options">
                <ComboBox x:Name="styleOptionsComboBox"
                          Margin="4"
                          VerticalAlignment="Center"
                          Width="100">
                    <ComboBoxItem Tag="{x:Null}"
                                  IsSelected="True"
                                  Content="Default Style"/>
                    <ComboBoxItem Tag="{StaticResource testTextBoxStyle}"
                                  Content="Custom Style"/>
                </ComboBox>
            </GroupBox>

            <GroupBox Header="TestText"
                      Width="300">
                <TextBox x:Name="inputTestText"
                         Margin="4"
                         VerticalContentAlignment="Center"
                         Text="{Binding ElementName=testTextBox, Path=Text}"/>
            </GroupBox>

            <GroupBox Header="FontSize">
                <StackPanel Orientation="Horizontal"
                            Margin="5">
                    <ComboBox x:Name="frontSizeSelect"
                              Margin="0,0,5,0">
                        <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                        <ComboBoxItem Tag="{Binding ElementName=fontSizeSlider, Path=Value}" Content="On"/>
                    </ComboBox>
                    <Slider x:Name="fontSizeSlider"
                        TickFrequency="2"
                        Minimum="10"
                        Maximum="100"
                        TickPlacement="BottomRight"
                        Width="200"/>
                    <TextBlock Text="{Binding ElementName=fontSizeSlider, Path=Value, StringFormat=Value: {0:0}}"/>
                </StackPanel>
            </GroupBox>

            <GroupBox Header="Height">
                <StackPanel Orientation="Horizontal"
                            Margin="5">
                    <ComboBox x:Name="heightSelect"
                              Margin="0,0,5,0">
                        <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                        <ComboBoxItem Tag="{Binding ElementName=heightSlider, Path=Value}" Content="On"/>
                    </ComboBox>
                    <Slider x:Name="heightSlider"
                        TickFrequency="4"
                        Minimum="20"
                        Maximum="200"
                        TickPlacement="BottomRight"
                        Width="200"/>
                    <TextBlock Text="{Binding ElementName=heightSlider, Path=Value, StringFormat=Value: {0:0}}"/>
                </StackPanel>
            </GroupBox>

            <GroupBox Header="VerticalAlignment">
                <ComboBox x:Name="verticalAlignment"
                          Margin="4">
                    <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                    <ComboBoxItem Tag="Bottom" Content="Bottom"/>
                    <ComboBoxItem Tag="Center" Content="Center"/>
                    <ComboBoxItem Tag="Stretch" Content="Stretch"/>
                    <ComboBoxItem Tag="Top" Content="Top"/>
                </ComboBox>
            </GroupBox>

            <GroupBox Header="VerticalContentAlignment">
                <ComboBox x:Name="verticalContentAlignment"
                          Margin="4">
                    <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                    <ComboBoxItem Tag="Bottom" Content="Bottom"/>
                    <ComboBoxItem Tag="Center" Content="Center"/>
                    <ComboBoxItem Tag="Stretch" Content="Stretch"/>
                    <ComboBoxItem Tag="Top" Content="Top"/>
                </ComboBox>
            </GroupBox>
       
            <GroupBox Header="HorizontalAlignment">
                <ComboBox x:Name="horizontalAlignment"
                        Margin="4">
                    <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                    <ComboBoxItem Tag="Center" Content="Center"/>
                    <ComboBoxItem Tag="Left" Content="Left"/>
                    <ComboBoxItem Tag="Right" Content="Right"/>
                    <ComboBoxItem Tag="Stretch" Content="Stretch"/>
                </ComboBox>
            </GroupBox>

            <GroupBox Header="HorizontalContentAlignment">
                <ComboBox x:Name="horizontalContentAlignment"
                        Margin="4">
                    <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                    <ComboBoxItem Tag="Center" Content="Center"/>
                    <ComboBoxItem Tag="Left" Content="Left"/>
                    <ComboBoxItem Tag="Right" Content="Right"/>
                    <ComboBoxItem Tag="Stretch" Content="Stretch"/>
                </ComboBox>
            </GroupBox>

            <GroupBox Header="Select FontFamily">
                <StackPanel Orientation="Horizontal"
                            Margin="4">
                    <ComboBox x:Name="selectFontFamily">
                        <ComboBoxItem Tag="{x:Null}" Content="x:Null" IsSelected="True"/>
                        <ComboBoxItem Tag="{Binding ElementName=fontFamilyTextBox, Path=Text}" Content="On"/>
                    </ComboBox>
                    <TextBox Margin="10,0,0,0" x:Name="fontFamilyTextBox" Width="300"/>
                </StackPanel>
            </GroupBox>
            
        </WrapPanel>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>

            <StackPanel>
                <GroupBox Header="Padding Left">
                    <Slider x:Name="paddingLeftSlider"
                            Minimum="0"
                            Maximum="20"
                            TickFrequency="1"
                            TickPlacement="BottomRight"
                            IsSnapToTickEnabled="True"
                            ValueChanged="PaddingSliderValueChanged"/>
                </GroupBox>

                <GroupBox Header="Padding Top">
                    <Slider x:Name="paddingTopSlider"
                            Minimum="0"
                            Maximum="20"
                            TickFrequency="1"
                            TickPlacement="BottomRight"
                            IsSnapToTickEnabled="True"
                            ValueChanged="PaddingSliderValueChanged"/>
                </GroupBox>
            </StackPanel>

            <GroupBox Grid.Column="1"
                      Header="Padding">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>

                    <TextBox Grid.Row="0"
                             Text="{Binding ElementName=paddingTopSlider, Path=Value}"
                             x:Name="paddingTopDisplay"
                             VerticalAlignment="Center"
                             HorizontalAlignment="Center"
                             BorderBrush="Black"/>
                    <Grid Grid.Row="1">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*"/>
                            <ColumnDefinition Width="2*"/>
                            <ColumnDefinition Width="1*"/>
                        </Grid.ColumnDefinitions>
                        <TextBox Grid.Column="0"
                                 Text="{Binding ElementName=paddingLeftSlider, Path=Value}"
                                 x:Name="paddingLeftDisplay"
                                 VerticalAlignment="Center"
                                 HorizontalAlignment="Center"
                                 BorderBrush="Black"/>
                        <TextBox Grid.Column="1"
                                 x:Name="paddingThicknesObject"
                                 VerticalAlignment="Center"
                                 HorizontalAlignment="Center"/>
                        <TextBox Grid.Column="2"
                                 Text="{Binding ElementName=paddingRightSlider, Path=Value}"
                                 x:Name="paddingRightDisplay"
                                 VerticalAlignment="Center"
                                 HorizontalAlignment="Center"
                                 BorderBrush="Black"/>
                    </Grid>
                    <TextBox Grid.Row="2"
                             Text="{Binding ElementName=paddingBottomSlider, Path=Value}"
                             x:Name="paddingBottomDisplay"
                             VerticalAlignment="Center"
                             HorizontalAlignment="Center"
                             BorderBrush="Black"/>
                </Grid>
            </GroupBox>

            <StackPanel Grid.Column="2">
                <GroupBox Header="Padding Right">
                    <Slider x:Name="paddingRightSlider"
                            Minimum="0"
                            Maximum="20"
                            TickFrequency="1"
                            TickPlacement="BottomRight"
                            IsSnapToTickEnabled="True"
                            ValueChanged="PaddingSliderValueChanged"/>
                </GroupBox>
                <GroupBox Header="Padding Bottom">
                    <Slider x:Name="paddingBottomSlider"
                            Minimum="0"
                            Maximum="20"
                            TickFrequency="1"
                            TickPlacement="BottomRight"
                            IsSnapToTickEnabled="True"
                            ValueChanged="PaddingSliderValueChanged"/>
                </GroupBox>
            </StackPanel>
        </Grid>
        <TextBox Grid.Row="2"
                 x:Name="testTextBox"
                 Style="{Binding ElementName=styleOptionsComboBox, Path=SelectedItem.Tag}"
                 Text="{Binding ElementName=inputTestText, Path=Text}"
                 FontSize="{Binding ElementName=frontSizeSelect, Path=SelectedItem.Tag}"
                 Height="{Binding ElementName=heightSelect, Path=SelectedItem.Tag}"
                 VerticalAlignment="{Binding ElementName=verticalAlignment, Path=SelectedItem.Tag}"
                 VerticalContentAlignment="{Binding ElementName=verticalContentAlignment, Path=SelectedItem.Tag}"
                 HorizontalAlignment="{Binding ElementName=horizontalAlignment, Path=SelectedItem.Tag}"
                 HorizontalContentAlignment="{Binding ElementName=horizontalContentAlignment, Path=SelectedItem.Tag}"
                 FontFamily="{Binding ElementName=selectFontFamily, Path=SelectedItem.Tag}">
        </TextBox>
        
    </Grid>
</Window>

和代码隐藏:

namespace TextBoxTestApp
{
    public partial class MainWindow : Window
    {
        private double _paddingLeft;
        private double _paddingRight;
        private double _paddingTop; 
        private double _paddingBottom;
        private Thickness _paddingTestTextBox;

        public MainWindow()
        {
            InitializeComponent();
            _paddingTestTextBox = new Thickness(0,0,0,0);
        }

        private void PaddingSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            ResetPaddingDisplayBorderBrush();

            var currentSlider = (Slider)sender;
            switch(currentSlider.Name)
            {
                case "paddingLeftSlider":
                    _paddingLeft = currentSlider.Value;
                    paddingLeftDisplay.BorderBrush = Brushes.Red;
                    break;
                case "paddingTopSlider":
                    _paddingTop = currentSlider.Value;
                    paddingTopDisplay.BorderBrush = Brushes.Red;
                    break;
                case "paddingRightSlider":
                    _paddingRight = currentSlider.Value;
                    paddingRightDisplay.BorderBrush = Brushes.Red;
                    break;
                case "paddingBottomSlider":
                    _paddingBottom = currentSlider.Value;
                    paddingBottomDisplay.BorderBrush = Brushes.Red;
                    break;
                    default: throw new ArgumentException("PaddingSliderValueChanged");
            }
            SetPaddingTestTextBox();
        }

        private void ResetPaddingDisplayBorderBrush()
        {
            paddingLeftDisplay.BorderBrush = Brushes.Black;
            paddingTopDisplay.BorderBrush= Brushes.Black;
            paddingRightDisplay.BorderBrush = Brushes.Black;
            paddingBottomDisplay.BorderBrush = Brushes.Black;
        }

        private void SetPaddingTestTextBox()
        {
            _paddingTestTextBox.Left = _paddingLeft;
            _paddingTestTextBox.Top = _paddingTop;
            _paddingTestTextBox.Right = _paddingRight;
            _paddingTestTextBox.Bottom = _paddingBottom;
            paddingThicknesObject.Text = _paddingTestTextBox.ToString();
            testTextBox.Padding = _paddingTestTextBox;
        }
    }
}