我有Border
Content
TextBlock
我想要在水平和垂直方向上完美居中。无论我尝试什么,它都不会看起来居中。我错过了什么?
使用文本顶部下面的代码比边框低19px,文本底部高出边框5px。它也偏离中心左侧或右侧,具体取决于我假设与字体相关的Text
值。
该解决方案适用于任何字体的不同文本(1-31)。
代码
<Grid Width="50" Height="50">
<Border BorderThickness="1" BorderBrush="Black">
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
</Grid>
结果
答案 0 :(得分:4)
那么,挑战被接受;-)这个解决方案基于以下想法:
一旦您意识到ScrollViewer的整个内容都已呈现,这很简单,所以这是我的UserControl XAML:
<UserControl x:Class="WpfApplication4.CenteredText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ScrollViewer x:Name="scroll"
IsHitTestVisible="False"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden" />
</Grid>
</UserControl>
将代码隐藏为:
public partial class CenteredText : UserControl
{
public CenteredText()
{
InitializeComponent();
}
public static readonly DependencyProperty ElementProperty = DependencyProperty
.Register("Element", typeof(FrameworkElement), typeof(CenteredText),
new PropertyMetadata(OnElementChanged));
private static void OnElementChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var elem = e.NewValue as FrameworkElement;
var ct = d as CenteredText;
if(elem != null)
{
elem.Loaded += ct.Content_Loaded;
ct.scroll.Content = elem;
}
}
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
void Content_Loaded(object sender, RoutedEventArgs e) /*...*/
}
此控件基本上是ContentControl
,允许一般处理内容的Loaded
事件。可能有一种更简单的方法,我不确定。
这个很简单。在Content_Loaded()
方法中:
void Content_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement elem = sender as FrameworkElement;
int w = (int)elem.ActualWidth;
int h = (int)elem.ActualHeight;
var rtb = new RenderTargetBitmap(w, h, 96, 96, PixelFormats.Pbgra32);
rtb.Render(elem);
/* glyph detection ... */
}
这非常容易,因为默认情况下TextBlock以完全透明的背景渲染,我们只对边界矩形感兴趣。这是通过一个单独的方法完成的:
bool TryFindGlyphs(BitmapSource src, out Rect rc)
{
int left = int.MaxValue;
int toRight = -1;
int top = int.MaxValue;
int toBottom = -1;
int w = src.PixelWidth;
int h = src.PixelHeight;
uint[] buf = new uint[w * h];
src.CopyPixels(buf, w * sizeof(uint), 0);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
// background is assumed to be fully transparent, i.e. 0x00000000 in Pbgra
if (buf[x + y * w] != 0)
{
if (x < left) left = x;
if (x > toRight) toRight = x;
if (y < top) top = y;
if (y > toBottom) toBottom = y;
}
}
}
rc = new Rect(left, top, toRight - left, toBottom - top);
return (toRight > left) && (toBottom > top);
}
上述方法试图找到不透明的最左边,最右边,最上面和最下面的像素,并将结果作为输出参数中的Rect返回。
稍后将在Content_Loaded
方法中执行此操作:
void Content_Loaded(object sender, RoutedEventArgs e)
{
/* render to bitmap ... */
Rect rc;
if (TryFindGlyphs(rtb, out rc))
{
if (rc.Height > this.scroll.ActualHeight || rc.Width > this.scroll.ActualWidth)
{
return; // todo: error handling
}
double desiredV = rc.Top - 0.5 * (this.scroll.ActualHeight - rc.Height);
double desiredH = rc.Left - 0.5 * (this.scroll.ActualWidth - rc.Width);
if (desiredV > 0)
{
this.scroll.ScrollToVerticalOffset(desiredV);
}
else
{
elem.Margin = new Thickness(elem.Margin.Left, elem.Margin.Top - desiredV,
elem.Margin.Right, elem.Margin.Bottom);
}
if (desiredH > 0)
{
this.scroll.ScrollToHorizontalOffset(desiredH);
}
else
{
elem.Margin = new Thickness(elem.Margin.Left - desiredH, elem.Margin.Top,
elem.Margin.Right, elem.Margin.Bottom);
}
}
}
使用以下策略更新此UI:
TextBlock
左上角对齐(默认情况下),ScrollViewer
仍位于初始(上/左)位置。但是有一个简单的解决方案:将所需的偏移量添加到Margin
的{{1}}。 TextBlock
控件的使用方法如下:
CenteredText
对于边框尺寸150x150和FontSize 150:
对于边框尺寸150x150和FontSize 50:
对于边框尺寸50x50和FontSize 50:
注意:存在1像素错误,文本左侧的空间比右侧空间厚1个像素或更薄。与顶部/底部间距相同。如果边框具有均匀宽度并且渲染文本为奇数宽度(未提供子像素完美性,抱歉),则会发生这种情况。
所提出的解决方案应该可以解决任何Font,FontSize和Text的1像素错误,并且易于使用。
如果您还没有注意到,那么对于与<Border BorderBrush="Black" BorderThickness="1" Width="150" Height="150">
<local:CenteredText>
<local:CenteredText.Element>
<TextBlock Text="31" FontSize="150" />
</local:CenteredText.Element>
</local:CenteredText>
</Border>
控件的FrameworkElement
属性一起使用的Elem
进行了非常有限的假设。因此,这也适用于任何具有透明背景且需要(接近)完美居中的元素。
答案 1 :(得分:0)
您所谈论的内容与您使用的特定字体(以及该字体中的字符)有关。不同的字体将具有不同的基线,高度和其他属性。为了解决这个问题,只需使用Padding
上的Border
或Margin
上的TextBlock
即可让它适合您想要的地方:
<Grid Width="50" Height="50">
<Border BorderThickness="1" BorderBrush="Black">
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="50" Margin="0,0,3,14" />
</Border>
</Grid>
注意:您还可以使用TextBlock.TextAlignment
Property调整文本内容的水平对齐方式。
答案 2 :(得分:0)
我将此添加为评论,但我没有足够的声誉:P
它偏离中心,因为您为网格指定的高度和宽度(50x50)太小而不能容纳50的字体大小。要么将大小增加到100x100,要么将字体大小降低到更小。
通过这样做来证明它们将在中心完美对齐 - 在Visual Studio的某个地方查看此代码。您将看到这些文本块的数量完全重叠。
<Grid Height="100" Width="100">
<Border BorderThickness="1" BorderBrush="Black" >
<TextBlock Text="13" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
<Border BorderThickness="1" BorderBrush="Black" >
<TextBlock Text="31" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="50"/>
</Border>
</Grid>
我希望这可以帮助你:)