WPF如何创建一个漂亮的字母波

Mar*_*ark 7 wpf layout animation text

我需要在我的WPF应用程序中创建一个看起来很浪漫的文本对象,我实际上假设会有一个"沿路径弯曲"类型的选项,但我在Blend中根本看不到一个.

我找到了一个教程,建议你需要将文本转换为逐字母的路径然后旋转它,但在我看来,这是非常可怕的,错误的空间和灵活性不足.

我基本上想要一个句子有一个动画波效果,我怎么能实现这个?

谢谢所有马克

Ray*_*rns 36

您正在寻找的是有效的非线性变换.Visual上的Transform属性只能进行线性变换.幸运的是,WPF的3D功能可以帮助你解决问题.您可以通过创建一个如下所示的简单自定义控件轻松完成您要查找的内容:

<local:DisplayOnPath Path="{Binding ...}" Content="Text to display" />
Run Code Online (Sandbox Code Playgroud)

这是怎么做的:

首先创建"DisplayOnPath"自定义控件.

  1. 使用Visual Studio的自定义控件模板创建它(确保您的程序集:ThemeInfo属性设置正确,所有这些)
  2. 添加类型的依赖项属性"Path" Geometry(使用wpfdp片段)
  3. 添加类型的只读依赖项属性"DisplayMesh" Geometry3D(使用wpfdpro片段)
  4. 添加一个PropertyChangedCallbackfor Path来调用"ComputeDisplayMesh"方法将Path转换为Geometry3D,然后从中设置DisplayMesh

它看起来像这样:

public class DisplayOnPath : ContentControl
{
  static DisplayOnPath()
  {
    DefaultStyleKeyProperty.OverrideMetadata ...
  }

  public Geometry Path { get { return (Geometry)GetValue(PathProperty) ...
  public static DependencyProperty PathProperty = ...  new UIElementMetadata
  {
    PropertyChangedCallback = (obj, e) =>
    {
      var displayOnPath = obj as DisplayOnPath;
      displayOnPath.DisplayMesh = ComputeDisplayMesh(displayOnPath.Path);
  }));

  public Geometry3D DisplayMesh { get { ... } private set { ... } }
  private static DependencyPropertyKey DisplayMeshPropertyKey = ...
  public static DependencyProperty DisplayMeshProperty = ...
}
Run Code Online (Sandbox Code Playgroud)

接下来,在任何自定义控件中创建样式和控件模板Themes/Generic.xaml(或ResourceDictionary由其包含).模板将包含以下内容:

<Style TargetType="{x:Type local:DisplayOnPath}">

  <Setter Property="Template">
    <Setter.Value>

      <ControlTemplate TargetType="{x:Type local:DisplayOnPath}">

        <Viewport3DVisual ...>

          <ModelVisual3D>
            <ModelVisual3D.Content>

              <GeometryModel3D Geometry="{Binding DisplayMesh, RelativeSource={RelativeSource TemplatedParent}}">
                <GeometryModel3D.Material>

                  <DiffuseMaterial>
                    <DiffuseMaterial.Brush>

                      <VisualBrush ...>
                        <VisualBrush.Visual>

                          <ContentPresenter />
                ...
Run Code Online (Sandbox Code Playgroud)

这样做是显示一个3D模型,它使用DisplayMesh进行定位,并使用控件的内容作为画笔材质.

请注意,您可能需要在Viewport3DVisual和VisualBrush上设置其他属性,以使布局按您希望的方式工作,并使内容视觉适当地拉伸.

剩下的就是"ComputeDisplayMesh"功能.如果您希望内容的顶部(您正在显示的单词)与路径垂直一定距离,则这是一个简单的映射.当然,您可以选择其他算法,例如创建并行路径并使用每个算法的百分比距离.

在任何情况下,基本算法都是相同的:

  1. 转换为PathGeometry使用PathGeometry.CreateFromGeometry
  2. 使用您选择的启发式选择网格中的适当数量的矩形'n'.也许从硬编码n = 50开始.
  3. 计算Positions矩形所有角的值.顶部有n + 1个角,底部有n + 1个角.每个底角都可以通过调用找到PathGeometry.GetPointAtFractionOfLength.这也会返回切线,因此也很容易找到顶角.
  4. 计算你的TriangleIndices.这是微不足道的.每个矩形将是两个三角形,因此每个矩形将有六个索引.
  5. 计算你的TextureCoordinates.这更加微不足道,因为它们都是0,1或i/n(其中i是矩形索引).

请注意,如果使用固定值n,则路径更改时唯一需要重新计算的是Posisions数组.其他一切都是固定的.

以下是此方法的主要部分:

var pathGeometry = PathGeometry.CreateFromGeometry(path);
int n=50;

// Compute points in 2D
var positions = new List<Point>();
for(int i=0; i<=n; i++)
{
  Point point, tangent;
  pathGeometry.GetPointAtFractionOfLength((double)i/n, out point, out tangent);
  var perpendicular = new Vector(tangent.Y, -tangent.X);
  perpendicular.Normalize();


  positions.Add(point + perpendicular * height); // Top corner
  positions.Add(point); // Bottom corner
}
// Convert to 3D by adding 0 'Z' value
mesh.Positions = new Point3DCollection(from p in positions select new Point3D(p.X, p.Y, 0));

// Now compute the triangle indices, same way
for(int i=0; i<n; i++)
{
  // First triangle
  mesh.TriangleIndices.Add(i*2+0);  // Upper left
  mesh.TriangleIndices.Add(i*2+2);  // Upper right
  mesh.TriangleIndices.Add(i*2+1);  // Lower left

  // Second triangle
  mesh.TriangleIndices.Add(i*2+1);  // Lower left
  mesh.TriangleIndices.Add(i*2+2);  // Upper right
  mesh.TriangleIndices.Add(i*2+3);  // Lower right
}
// Add code here to create the TextureCoordinates
Run Code Online (Sandbox Code Playgroud)

就是这样.大部分代码都是上面写的.我留给你填写其余部分.

顺便说一句,请注意,通过创造"Z"值,您可以获得一些真正令人敬畏的效果.

更新

Mark为此实现了代码并遇到了三个问题.以下是问题和解决方案:

  1. 我在三角形#1的TriangleIndices命令中犯了一个错误.它在上面得到纠正.我最初的那些指数在左上角 - 左下角 - 右上角.通过逆时针绕三角形,我们实际上看到三角形的背面,所以没有画任何东西.通过简单地改变索引的顺序,我们顺时针转动,以便三角形可见.

  2. GeometryModel3D上的绑定最初是一个TemplateBinding.这不起作用,因为TemplateBinding不会以相同的方式处理更新.将其更改为常规绑定可以解决问题.

  3. 3D的坐标系是+ Y是向上的,而对于2D + Y是向下的,所以路径是颠倒的.这可以通过在代码中否定Y或者根据您的喜好添加RenderTransformon 来解决ViewPort3DVisual.我个人更喜欢RenderTransform,因为它使ComputeDisplayMesh代码更具可读性.

以下是马克代码的快照,其中包含了我认为我们共享的情绪:

动画文本快照"StackOverflowIsFun"http://rayburnsresume.com/StackOverflowImages/WavyLetters.png

  • 为您在答案中投入的大量精力+1.奖励! (6认同)
  • +1.我不明白你答案的一半,但是为了这么彻底的解释,你应该得到它;) (4认同)

Pat*_*lug 8

您可能想查看Charles Petzold的MSDN文章使用WPF在路径上渲染文本(此处存档版本).

波浪文字

我发现这篇文章非常有用,他还提供了一个使用动画的示例.