【问题标题】:Creating rotated text in WPF在 WPF 中创建旋转文本
【发布时间】:2012-09-27 19:17:28
【问题描述】:

我正在尝试创建一些旋转文本并将该图像保存到 PNG 文件中。生成的 PNG 不应大于所需(或最小填充)。只要有没有旋转,我就可以让它工作,但是一旦我旋转文本,它就会在文件中被剪掉。我确信这与调整 RotateTransform 的 CenterX 和 CenterY 或创建 TranslateTransform 有关,但我找不到任何关于如何正确执行此操作的信息,并且我的试错测试已经变成了试验-和-沮丧

我的示例代码如下。我正在寻找一种可以使用任意角度而不仅仅是 -45 度的解决方案。

最后,如果有人知道如何满足这些要求,但说使用“旧式”图形对象而不是 WPF 工具,我也愿意接受这种解决方案。

private static void CreateImageFile()
{
    FormattedText ft;
    Geometry textBox;
    string fontName;
    Typeface face;
    DrawingVisual viz;
    RotateTransform rt;
    TranslateTransform tt;
    Rect rect;
    RenderTargetBitmap bmp;
    PngBitmapEncoder encoder;

    ft = CreateText("Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor", "Verdana", 12, false, false);
    textBox = ft.BuildHighlightGeometry(new Point());

    fontName = "Arial";
    face = new Typeface(fontName);

    // now create the visual we'll draw them to
    viz = new DrawingVisual();
    rt = new RotateTransform() { Angle = -45 };
    rect = rt.TransformBounds(ft.BuildHighlightGeometry(new Point(0, 0)).Bounds);

    using (DrawingContext dc = viz.RenderOpen())
    {
        dc.PushTransform(rt);
        dc.DrawText(ft, new Point(0, 0));
        dc.Pop();
    }

    bmp = new RenderTargetBitmap((int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Pbgra32);
    bmp.Render(viz);

    encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bmp));
    using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
        encoder.Save(file);
}

private static FormattedText CreateText(string text, string typeface, double fontSize, bool bold, bool italic)
{
    FontStyle fontStyle = FontStyles.Normal;
    FontWeight fontWeight = FontWeights.Medium;

    if (bold == true) fontWeight = FontWeights.Bold;
    if (italic == true) fontStyle = FontStyles.Italic;

    // Create the formatted text based on the properties set.
    FormattedText formattedText = new FormattedText(
        text,
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        new Typeface(new FontFamily(typeface),
            fontStyle,
            fontWeight,
            FontStretches.Normal),
        fontSize,
        Brushes.Black, // This brush does not matter since we use the geometry of the text. 
        null,
        TextFormattingMode.Display
        );

    return formattedText;
}

更新

根据下面的一些建议,我决定在 GUI 中尝试不同的策略和实验。我创建了一个这样的窗口:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="160" Width="160" Loaded="Window_Loaded">
    <Grid>
        <Canvas Name="WorkCanvas" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock RenderTransformOrigin="0.5,0.5">
                <TextBlock.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform Angle="-45"/>
                        <TranslateTransform/>
                    </TransformGroup>
                </TextBlock.RenderTransform>This is a test</TextBlock>
        </Canvas>
    </Grid>
</Window>

如您所见,它同时使用了RotateTransform 和建议的RenderTransformOrigin,结果如下图所示。而且,如您所见,文本确实穿过Canvas 的中间。 那个似乎是我的全部问题。如何旋转文本使其正确居中。


更新 2

我决定这次尝试使用Grid 而不是Canvas,我现在可以让文本在网格中正确居中,但由于我不能使用FormattedText 对象,我似乎无法测量实际的边界框。 TextBlockGrid 的所有测量值都会返回,就好像它根本没有旋转一样(查看 ActualWidthActualHeightDesiredSize)。如果我无法获得旋转的边界框大小,我无法保存 PNG 而不会被剪裁。

哦,我尝试在未旋转的网格中旋转文本并旋转网格本身,在尝试确定尺寸时都给出相同的结果。

【问题讨论】:

  • 你考虑过故事板吗?您需要一个独立的 png 吗?
  • @Blam 不,我没有考虑故事板。故事板将如何获得独立的图像文件。是的,我需要一个文件,这不是为了在屏幕上显示。它用于最终将添加到 PDF 文档中的水印。
  • 那么不是故事板是行不通的。

标签: c# wpf text


【解决方案1】:

您可以尝试将文本包装在具有 rendertransformOrigin 的元素中。让您更改该元素。试试画布或网格。

【讨论】:

  • 我刚才试过了,但问题是 FormattedText 对象不是 UIObject,所以我不能将它添加到 Grid(或者可能是 Canvas)。我尝试使用 FormattedText 对象的原因是我需要测量文本的像素大小,以便将其保存为 PNG 以供以后使用。并且图像文件需要很少或没有边距(对于仅需要 2 x 1 英寸的文本,总是保存 8.5 x 11 的图像文件是不可接受的)。只要满足最终图像要求,如果您能想到另一种方式,我就不会以这种方式(如上面的示例所示)这样做。
【解决方案2】:

我认为您缺少的是您需要将 RenderTransformOrigin 设置为 0.5,0.5,以便您的旋转变换围绕图像的中心而不是左上边缘。

更新

响应您的上述更新。问题是使用画布。如果你完全删除你的变换,你会看到你的 TextBlock 不是从一开始就居中的。它实际上是围绕它的中心旋转的,只是中心不是画布的中心。试试这个:

<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock TextAlignment="Center" RenderTransformOrigin="0.5,0.5">
            <TextBlock.RenderTransform>
                    <RotateTransform Angle="-45"/>
            </TextBlock.RenderTransform>
            This is a test
        </TextBlock>
</Grid>

【讨论】:

  • 我在上面的示例上下文中没有看到 RenderTransformOrigin,但是如果我说 rt.CenterX,它似乎来自 MSDN 描述= textBox.Bounds.Width / 2;rt.CenterY = textBox.Bounds.Height / 2;。虽然这给了我一个更好的答案,但它仍然被剪掉了。
  • 从头开始。经过更多调查,RenderTransformOrigin 并不(对我而言)与设置 CenterX 和 CenterY 相同。但是,无论如何, FormattedText 没有 RenderTransformOrigin 属性。其他想法?
  • 正如@kbo4sho88 建议的那样,您可能需要将它放在 UIElement 中。您可能会遇到的另一个问题是,您需要确保位图的宽度和高度足以在每个方向包含您的文本。
  • 查看我为@kbo4sho88 留下的评论
【解决方案3】:

经过多次摸索,在 Matt 和 kbo4sho88 的帮助下,我终于找到了正确的方法。除了其他发帖人的帮助外,我终于发现我需要调用 TransformToVisual 和 TransformBounds 来获取正确文件大小所需的边界框。但是,在那之前,我不得不调用 Measure 和 Arrange,因为这些对象没有显示在屏幕上。

呼!

private static void CreateImageFile()
{
    Grid workGrid;
    TextBlock workTextBlock;
    RenderTargetBitmap bitmap;
    PngBitmapEncoder encoder;
    Rect textBlockBounds;
    GeneralTransform transform;

    workGrid = new Grid()
    {
        VerticalAlignment = VerticalAlignment.Center,
        HorizontalAlignment = HorizontalAlignment.Center
    };

    workTextBlock = new TextBlock()
    {
        Text = "Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor",
        FontFamily = new FontFamily("Verdana"),
        FontSize = 36,
        TextAlignment = TextAlignment.Center,
        RenderTransformOrigin = new Point(0.5, 0.5),
        LayoutTransform = new RotateTransform(-45)
    };

    workGrid.Children.Add(workTextBlock);

    /*
     * We now must measure and arrange the controls we just created to fill in the details (like
     * ActualWidth and ActualHeight before we call TransformToVisual() and TransformBounds()
     */
    workGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    workGrid.Arrange(new Rect(0, 0, workGrid.DesiredSize.Width, workGrid.DesiredSize.Height));

    transform = workTextBlock.TransformToVisual(workGrid);
    textBlockBounds = transform.TransformBounds(new Rect(0, 0, workTextBlock.ActualWidth, workTextBlock.ActualHeight));

    /*
     * Now, create the bitmap that will be used to save the image. We will make the image the 
     * height and width we need at 96DPI and 32-bit RGBA (so the background will be transparent).
     */
    bitmap = new RenderTargetBitmap((int)textBlockBounds.Width, (int)textBlockBounds.Height, 96, 96, PixelFormats.Pbgra32);
    bitmap.Render(workGrid);

    encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bitmap));
    using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
        encoder.Save(file);
}

当然,这只是一个示例(但有效)方法,最后一个将被参数化。

谜题的最后一部分在WPF: Getting new coordinates after a Rotation找到

【讨论】:

    【解决方案4】:

    我通常处理自定义渲染的方式是使用我自己的FrameworkElement 并覆盖OnRender 方法。所以,举个例子:

    using System;                  
    using System.Windows;           //  For the FrameworkElement baseclass
    using System.Windows.Media;     //  To get the drawing context
    
    public class CustomThing : FrameworkElement
    {
        //  Constructor
        CustomThing() 
        {
        }
    
        //  Custom render code called whenever control is invalidated
        protected override OnRender(DrawingContext context)
        {
            if (!this.IsVisible || this.ActualHeight <= 0 || this.ActualWidth <= 0)
            {
                return;   //  Don't do anything if this thing isn't renderable
            }
            Typeface tf = new Typeface(this.FontFamily, FontStyles.Normal, this.FontWeight, FontStretches.Normal);                    
            FormattedText fText = new FormattedText(this.Text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tf, this.FontSize, this.Foreground);
    
            //  You could have accessors so that the various properties such as Fonts, etc. are 
            //  Properties of the class, and using DependencyProperties, they can be set so they
            //  automatically cause an invalidation when they change.
    
            double txWidth = fText.Width;
            double txHeight = fText.Height;
            //  This measures the text
          
            double w = this.ActualWidth;
            double h = this.ActualHeight;
            double w2 = w / 2.0;
            double h2 = h / 2.0;
            //  Get the center point for the rotation
            //  In this case, the center of the control
    
            Transform trans = new RotateTransform(-90.0, w2, h2);
            //  The transform is for counter-clockwise 90 degrees, centered
            //  in the center of the control.
    
            context.PushTransform(trans);
            //  All drawing operations will be performed with the
            //  transformation applied
    
            Point txPos = new Point(w2 - (txWidth / 2.0), h2 - (txHeight / 2.0));
            //  The render origin for the text is the upper left
            //  hand corner of the bounding box.  This uses the same origin for
            //  the rotation, then shifts it by half the dimensions of the text.
    
            context.DrawText(fText, txPos);
    
            context.Pop();
            //  The pop method is only needed if you need to continue work
            //  with the drawing context and don't want the transform
            //  operating on future rendering.
        }   
    }
    

    还有其他细节,例如,如果您希望控件与鼠标交互(包括使用 System.Windows.Input),那么您需要通过为整个控件区域绘制背景颜色来开始渲染。最主要的是你不需要测量旋转文本的形状,只需要测量未旋转的文本。如果您想将最终文本放置在控件中心以外的任何其他位置,只需确保您的旋转中心也与文本偏移量相同。

    我通常做的另一件事是使用依赖属性,以便可以在 XAML 中完成所有样式设置。这样,可以在设计器中预览自定义对象,同时动态更改属性,项目的典型条目如下所示:

    public static readonly RoutedEvent TextChangedEvent = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<string>), typeof(CustomThing));
    
    public event RoutedPropertyChangedEventHandler<string> TextChanged
    {
        add { AddHandler(TextChangedEvent, value); }
        remove { RemoveHandler(TextChangedEvent, value); }
    }
    
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomThing), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnTextChanged)));
    
    public string Text
    {
        get => (string)GetValue(TextProperty);
        set => SetValue(TextProperty, value);
    }
    
    private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        CustomThing cntrl = (CustomThing)obj;
        RoutedPropertyChangedEventArgs<string> e = new RoutedPropertyChangedEventArgs<string>((string)args.OldValue, (string)args.NewValue, TextChangedEvent);
        cntrl.OnTextChanged(e);
    }
    
    protected virtual void OnTextChanged(RoutedPropertyChangedEventArgs<string> e)
    {
        RaiseEvent(e);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-25
      • 2016-03-28
      • 2011-06-28
      • 1970-01-01
      相关资源
      最近更新 更多