我有必要建立一个波浪卷发寻找文本对象在我的WPF应用程序一个好看的波浪,而我其实是假设会有类型的选项是“沿着路径弯曲”,但在Blend我没有看到任何一个。WPF如何创建字母
我发现了一个教程,建议你需要给你的信的文本转换为路径字母,然后围绕其旋转,但在我看来是完全可怕的,要很大的错误,并且没有足够的灵活性。
我想基本上是一个句子有一个动画波浪效果,我怎么能做到这一点?
感谢所有 马克
我有必要建立一个波浪卷发寻找文本对象在我的WPF应用程序一个好看的波浪,而我其实是假设会有类型的选项是“沿着路径弯曲”,但在Blend我没有看到任何一个。WPF如何创建字母
我发现了一个教程,建议你需要给你的信的文本转换为路径字母,然后围绕其旋转,但在我看来是完全可怕的,要很大的错误,并且没有足够的灵活性。
我想基本上是一个句子有一个动画波浪效果,我怎么能做到这一点?
感谢所有 马克
您可能想查看Charles Petzold的MSDN文章Render Text On A Path With WPF(archived version here)。
我发现这篇文章非常有用,他还提供了他使用动画的样本。
你在找什么是有效的非线性变换。 Visual上的Transform属性只能进行线性变换。幸运的是,WPF的3D功能可以帮助您解决问题。您可以轻松地完成你通过创建,将这样使用一个简单的自定义控件找什么:
<local:DisplayOnPath Path="{Binding ...}" Content="Text to display" />
这里是如何做到这一点:
首先创建“DisplayOnPath”自定义控件。
Geometry
的依赖属性的“路径”(使用wpfdp片段)Geometry3D
类型的只读依赖属性“DisplayMesh”(使用wpfdpro片段)PropertyChangedCallback
的路径称之为“ComputeDisplayMesh”方法,将路径转换为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 = ...
}
下一页创建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 />
...
这样做是显示一个使用DisplayMesh的位置,并使用控件的内容作为刷材料的3D模型。
请注意,您可能需要在Viewport3DVisual和VisualBrush上设置其他属性以使布局以您想要的方式工作,并使内容视觉得到适当拉伸。
所有剩下的是“ComputeDisplayMesh”功能。如果您希望内容的顶部(您显示的字词)与路径相距一定距离,则这是一个简单的映射。当然,也可以选择其他算法,例如创建并行路径并沿每个路径使用百分比距离。
在任何情况下,基本算法是相同的:
PathGeometry
使用PathGeometry.CreateFromGeometry
Positions
值。顶部有n + 1个角落,底部有n + 1个角落。请致电PathGeometry.GetPointAtFractionOfLength
找到每个底角。这也返回一个切线,所以很容易找到顶角。TriangleIndices
。这是微不足道的。每个矩形都是两个三角形,所以每个矩形会有六个索引。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
这就是它。大部分代码都写在上面。我把它留给你来填补其余的部分。顺便说一句,请注意,通过创造'Z'值,你可以得到一些真正令人敬畏的效果。
更新
马克实现的代码,这和遇到的三个问题。下面是他们的问题和解决方案:
我在三角形#1的TriangleIndices顺序中犯了一个错误。它在上面得到纠正。我原本有这些指标左上角 - 左下角 - 右上角。通过逆时针绕过三角形,我们实际上看到了三角形的背面,所以没有画任何东西。通过简单地改变索引的顺序,我们顺时针旋转,因此三角形是可见的。
GeometryModel3D上的绑定最初是TemplateBinding
。这不起作用,因为TemplateBinding不以相同的方式处理更新。将其更改为常规绑定可解决问题。
3D的坐标系是+ Y向上,而对于2D + Y向下,所以路径出现颠倒。这可以通过在代码中否定Y或在ViewPort3DVisual
上添加RenderTransform
来解决,如您所愿。我个人更喜欢RenderTransform,因为它使ComputeDisplayMesh代码更具可读性。
这里是马克的代码动画情绪的快照我想大家都分享到:
+1为你付出了巨大的努力,你把你的答案。奖励! – 2009-12-24 01:12:30
+1。我不明白你的答案的一半,但为了这样一个彻底的解释,你应得的;) – 2009-12-24 01:17:19
这是一个很棒的回复,我需要一些时间消化这个并进入它...我会尽快回复你我有机会玩。真的,非常感谢你的努力,如果我能够按照我的想法做到这一点,那么这对我的项目来说将是一个巨大的胜利,你不知道这会让人感到兴奋:) – Mark 2009-12-24 01:23:06
我想我会真的发表了我的进度的详细信息,以便我们能够摆脱评论(这不格式化为好:))
这里是我的主窗口:
<Window.Resources>
<Style TargetType="{x:Type local:DisplayOnPath}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DisplayOnPath}">
<Viewport3D>
<Viewport3D.Camera>
<PerspectiveCamera FieldOfView="60"
FarPlaneDistance="1000"
NearPlaneDistance="10"
Position="0,0,300"
LookDirection="0,0,-1"
UpDirection="0,1,0"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<AmbientLight Color="#ffffff" />
<GeometryModel3D Geometry="{TemplateBinding DisplayMesh}">
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="Red" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Storyboard x:Key="movepath">
<PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[4].(LineSegment.Point)">
<SplinePointKeyFrame KeyTime="00:00:01" Value="181.5,81.5"/>
</PointAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[3].(LineSegment.Point)">
<SplinePointKeyFrame KeyTime="00:00:01" Value="141.5,69.5"/>
</PointAnimationUsingKeyFrames>
<PointAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="p1" Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[1].(LineSegment.Point)">
<SplinePointKeyFrame KeyTime="00:00:01" Value="62.5,49.5"/>
</PointAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource movepath}"/>
</EventTrigger>
</Window.Triggers>
<Grid x:Name="grid1">
<Path x:Name="p1" Stroke="Black" Margin="238.5,156.5,331.5,0" VerticalAlignment="Top" Height="82">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="0.5,0.5">
<LineSegment Point="44.5,15.5"/>
<LineSegment Point="73.5,30.5"/>
<LineSegment Point="91.5,56.5"/>
<LineSegment Point="139.5,53.5"/>
<LineSegment Point="161,80"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
<local:DisplayOnPath x:Name="wave1" Path="{Binding Data, ElementName=p1, Mode=Default}" />
</Grid>
然后,我有实际的用户控件:
public partial class DisplayOnPath : UserControl
{
public MeshGeometry3D DisplayMesh
{
get { return (MeshGeometry3D)GetValue(DisplayMeshProperty); }
set { SetValue(DisplayMeshProperty, value); }
}
public Geometry Path
{
get { return (Geometry)GetValue(PathProperty); }
set { SetValue(PathProperty, value); }
}
public static readonly DependencyProperty DisplayMeshProperty =
DependencyProperty.Register("DisplayMesh", typeof(MeshGeometry3D), typeof(DisplayOnPath), new FrameworkPropertyMetadata(new MeshGeometry3D(), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register("Path",
typeof(Geometry),
typeof(DisplayOnPath),
new PropertyMetadata()
{
PropertyChangedCallback = (obj, e) =>
{
var displayOnPath = obj as DisplayOnPath;
displayOnPath.DisplayMesh = ComputeDisplayMesh(displayOnPath.Path);
}
}
);
private static MeshGeometry3D ComputeDisplayMesh(Geometry path)
{
var mesh = new MeshGeometry3D();
var pathGeometry = PathGeometry.CreateFromGeometry(path);
int n = 50;
int height = 10;
// Compute points in 2D
var positions = new List<Point>();
for (int i = 0; i <= n; i++)
{
Point point, tangent;
pathGeometry.GetPointAtFractionLength((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 + 1); // Lower left
mesh.TriangleIndices.Add(i * 2 + 2); // Upper right
// 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
}
for (int i = 0; i <= n; i++)
{
for (int j = 0; j < 2; j++)
{
mesh.TextureCoordinates.Add(new Point((double) i/n, j));
}
}
//Console.WriteLine("Positions=\"" + mesh.Positions + "\"\nTriangleIndices=\"" + mesh.TriangleIndices +
// "\"\nTextureCoordinates=\"" + mesh.TextureCoordinates + "\"");
return mesh;
}
static DisplayOnPath()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DisplayOnPath), new FrameworkPropertyMetadata(typeof(DisplayOnPath)));
}
public DisplayOnPath()
{
InitializeComponent();
}
}
目前原样,这并不比呈现的路径的任何其他。
,但如果你的窗口加载之后获得的wave1
网的详细信息,然后更换绑定进行硬编码值,你会得到这样的:http://img199.yfrog.com/i/path1.png/
其中有2分主要的问题,因为它是:
你应该编辑你的原始问题,而不是添加一个答案(因为这不是一个答案) – 2010-01-16 10:57:35
感谢您的链接,我不能相信这篇文章没有出现在我的谷歌搜索... – Mark 2010-01-18 11:15:53
不用担心 - 很高兴我可以帮助:-) – 2010-01-19 00:58:30