WPF绘制动态曲线
Direct2D
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Image Name="DxImage"/>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using Device = SharpDX.Direct3D11.Device;
using Factory = SharpDX.Direct2D1.Factory;
using AlphaModeD2D = SharpDX.Direct2D1.AlphaMode;
using SharpDX.DirectWrite;
using System.Windows.Media;
using FactoryType = SharpDX.Direct2D1.FactoryType;
using TextAntialiasMode = SharpDX.Direct2D1.TextAntialiasMode;
using DashStyle = SharpDX.Direct2D1.DashStyle;
using SolidColorBrush = SharpDX.Direct2D1.SolidColorBrush;
using PathGeometry = SharpDX.Direct2D1.PathGeometry;
namespace WpfApp2
{
public partial class MainWindow : Window
{
private Device device;
private SwapChain swapChain;
private RenderTarget renderTarget;
private Factory factory;
private SharpDX.DirectWrite.Factory writeFactory;
private TextFormat textFormat;
private bool isResizing = false;
private StrokeStyle strokeStyle;
private DispatcherTimer timer;
private float phase = 0;
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
Unloaded += MainWindow_Unloaded;
SizeChanged += MainWindow_SizeChanged;
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(16); // 约60 FPS
timer.Tick += Timer_Tick;
timer.Start();
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
InitializeSharpDX();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
private void MainWindow_Unloaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering -= CompositionTarget_Rendering;
DisposeResources();
}
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (swapChain != null && !isResizing)
{
isResizing = true;
try
{
renderTarget?.Dispose();
swapChain.ResizeBuffers(1, (int)ActualWidth, (int)ActualHeight, Format.B8G8R8A8_UNorm, SwapChainFlags.None);
CreateRenderTarget();
UpdateTextFormat();
}
finally
{
isResizing = false;
}
}
}
private float speed = 0.15f;
private void Timer_Tick(object sender, EventArgs e)
{
phase += speed;
if (phase > Math.PI * 2)
phase -= (float)(Math.PI * 2);
InvalidateVisual();
}
private void InitializeSharpDX()
{
factory = new Factory(FactoryType.SingleThreaded);
writeFactory = new SharpDX.DirectWrite.Factory();
var swapChainDescription = new SwapChainDescription
{
BufferCount = 1,
ModeDescription = new ModeDescription((int)ActualWidth, (int)ActualHeight, new Rational(60, 1), Format.B8G8R8A8_UNorm),
IsWindowed = true,
OutputHandle = new WindowInteropHelper(this).Handle,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
Device.CreateWithSwapChain(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.BgraSupport, swapChainDescription, out device, out swapChain);
UpdateTextFormat();
CreateRenderTarget();
CreateStrokeStyle();
}
private void UpdateTextFormat()
{
textFormat?.Dispose();
float fontSize = (float)(Math.Min(ActualWidth, ActualHeight) * 0.02);
textFormat = new TextFormat(writeFactory, "Arial", fontSize)
{
TextAlignment = SharpDX.DirectWrite.TextAlignment.Center,
ParagraphAlignment = SharpDX.DirectWrite.ParagraphAlignment.Center
};
}
private void CreateRenderTarget()
{
using (var surface = swapChain.GetBackBuffer<Surface>(0))
{
var properties = new RenderTargetProperties(new SharpDX.Direct2D1.PixelFormat(Format.Unknown, AlphaModeD2D.Premultiplied));
renderTarget = new RenderTarget(factory, surface, properties)
{
AntialiasMode = AntialiasMode.PerPrimitive,
TextAntialiasMode = TextAntialiasMode.Cleartype
};
}
}
private void CreateStrokeStyle()
{
strokeStyle?.Dispose();
strokeStyle = new StrokeStyle(factory, new StrokeStyleProperties
{
DashStyle = DashStyle.Solid,
LineJoin = LineJoin.Round,
StartCap = CapStyle.Round,
EndCap = CapStyle.Round
});
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (renderTarget == null || isResizing)
return;
renderTarget.BeginDraw();
DrawScene();
renderTarget.EndDraw();
swapChain.Present(1, PresentFlags.None);
}
private void DrawScene()
{
renderTarget.Clear(new RawColor4(1, 1, 1, 1)); // 清空画布,设置为白色背景
using (var axisBrush = new SharpDX.Direct2D1.SolidColorBrush(renderTarget, new RawColor4(0, 0, 0, 1)))
using (var curveBrush = new SolidColorBrush(renderTarget, new RawColor4(0, 0, 1, 1)))
using (var textBrush = new SolidColorBrush(renderTarget, new RawColor4(0, 0, 0, 1)))
{
float width = (float)renderTarget.Size.Width;
float height = (float)renderTarget.Size.Height;
float margin = Math.Min(width, height) * 0.1f;
float axisLength = Math.Min(width, height) * 0.8f;
// 画 X 轴
renderTarget.DrawLine(
new RawVector2(margin, height / 2),
new RawVector2(width - margin, height / 2),
axisBrush, 2);
// 画 Y 轴
renderTarget.DrawLine(
new RawVector2(width / 2, margin),
new RawVector2(width / 2, height - margin),
axisBrush, 2);
// 添加 X 轴和 Y 轴的刻度线和标签
AddAxisTicksAndLabels(width, height, margin, axisBrush, textBrush);
// 生成正弦波的点
var points = GenerateSineWavePoints(width, height, margin, phase);
using (var geometry = new PathGeometry(factory))
{
var sink = geometry.Open();
sink.BeginFigure(points[0], FigureBegin.Hollow);
for (int i = 1; i < points.Length; i++)
{
sink.AddLine(points[i]);
}
sink.EndFigure(FigureEnd.Open);
sink.Close();
renderTarget.DrawGeometry(geometry, curveBrush, 2, null);
}
}
}
private void AddAxisTicksAndLabels(float width, float height, float margin, SharpDX.Direct2D1.SolidColorBrush axisBrush, SolidColorBrush textBrush)
{
int numberOfTicks = 10;
float xTickSpacing = (width - 2 * margin) / numberOfTicks;
float yTickSpacing = (height - 2 * margin) / numberOfTicks;
for (int i = 0; i <= numberOfTicks; i++)
{
// X 轴刻度线
float x = margin + i * xTickSpacing;
renderTarget.DrawLine(new RawVector2(x, height / 2 - 5), new RawVector2(x, height / 2 + 5), axisBrush, 2);
// X 轴标签
DrawText(renderTarget, textBrush, x.ToString(), new RawVector2(x, height / 2 + 10));
// Y 轴刻度线
float y = margin + i * yTickSpacing;
renderTarget.DrawLine(new RawVector2(width / 2 - 5, y), new RawVector2(width / 2 + 5, y), axisBrush, 2);
// Y 轴标签
DrawText(renderTarget, textBrush, ((height - y) / (height / 2) * 10).ToString(), new RawVector2(width / 2 + 10, y));
}
}
private void DrawText(RenderTarget renderTarget, SolidColorBrush textBrush, string text, RawVector2 position)
{
renderTarget.DrawText(text, textFormat, new RawRectangleF(position.X, position.Y, position.X + 50, position.Y + 20), textBrush);
}
private RawVector2[] GenerateSineWavePoints(float width, float height, float margin, float phase)
{
int pointCount = 200;
RawVector2[] points = new RawVector2[pointCount];
float dx = (width - 2 * margin) / (pointCount - 1);
float amplitude = height / 4;
for (int i = 0; i < pointCount; i++)
{
float x = margin + i * dx;
float y = height / 2 + (float)Math.Sin(i * 0.05 + phase) * amplitude;
points[i] = new RawVector2(x, y);
}
return points;
}
private void DisposeResources()
{
renderTarget?.Dispose();
swapChain?.Dispose();
device?.Dispose();
factory?.Dispose();
writeFactory?.Dispose();
textFormat?.Dispose();
strokeStyle?.Dispose();
}
}
}