WPF丝滑渲染图表

在这里插入图片描述

原理

同步到显示刷新率:

CompositionTarget.Rendering 事件会在每次 WPF 准备渲染一帧时触发。这意味着该事件会与显示器的刷新率同步,通常为每秒 60 帧(60 Hz),从而提供更平滑的动画效果。
减少延迟和抖动:

使用 DispatcherTimer 时,定时器触发的时间间隔可能会受到其他操作或线程调度的影响,导致帧时间不一致,进而造成动画抖动或不流畅。CompositionTarget.Rendering 事件则直接与 WPF 的渲染系统集成,减少了这种不确定性。
高效的帧管理:

CompositionTarget.Rendering 事件可以确保在每个显示刷新周期内都能执行绘制逻辑,从而保证动画的每一帧都能够准确渲染。

<Window x:Class="WpfApp5.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:WpfApp5"
        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.Media;
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 FactoryType = SharpDX.Direct2D1.FactoryType;
using TextAntialiasMode = SharpDX.Direct2D1.TextAntialiasMode;
using DashStyle = SharpDX.Direct2D1.DashStyle;
using SolidColorBrush = SharpDX.Direct2D1.SolidColorBrush;
using PathGeometry = SharpDX.Direct2D1.PathGeometry;
using System.DirectoryServices.ActiveDirectory;
using System.Windows.Automation.Text;
using CapStyle = SharpDX.Direct2D1.CapStyle;

namespace WpfApp5
{
	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 float phase = 0;
		private float speed = 0.05f;

		public MainWindow()
		{
			InitializeComponent();
			Loaded += MainWindow_Loaded;
			Unloaded += MainWindow_Unloaded;
			SizeChanged += MainWindow_SizeChanged;
		}

		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 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;

			phase += speed;
			if (phase > Math.PI * 2)
				phase -= (float)(Math.PI * 2);

			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 = 100;
			float step = (width - 2 * margin) / (pointCount - 1);
			var points = new RawVector2[pointCount];

			for (int i = 0; i < pointCount; i++)
			{
				float x = margin + i * step;
				float y = (float)(height / 2 - (height / 4 * Math.Sin((i * 0.1) + phase)));
				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();
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值