You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将含图表的WPF UserControl保存为矢量图形文件?

解决WPF UserControl导出矢量图形的问题

我明白你现在的困境——想把WPF UserControl里的图表导出成矢量格式(比如WMF),但目前导出的内容是空的,又不想重写所有图形类。我来帮你分析问题并给出可行的方案:

你当前代码的核心问题

你把UserControl通过VisualHost加到Canvas里,但导出时看不到内容,大概率是两个原因:

  1. UserControl没有完成布局渲染:WPF控件需要经过Measure、Arrange、UpdateLayout三个步骤才能正确计算尺寸并渲染内容,直接加到Canvas里可能还没完成这个流程,导致导出时是空的。
  2. 你的WPFPainter只处理Canvas的子Shape:UserControl是复杂的Visual对象,不是普通的Shape,你的绘制工具可能没有遍历处理VisualHost里的控件内容。

方案一:用WPF原生XPS导出(最推荐,无需重写代码)

XPS是WPF原生支持的矢量格式,完全保留图表的矢量特性,操作简单,不需要修改现有图形类。直接用XpsDocument导出UserControl即可:

using System.IO;
using System.Windows.Xps.Packaging;

public void ExportChartAsXps(UserControl chartControl, string outputPath)
{
    // 先确保控件完成布局渲染
    chartControl.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    chartControl.Arrange(new Rect(chartControl.DesiredSize));
    chartControl.UpdateLayout();

    // 导出为XPS
    using (var xpsDoc = new XpsDocument(outputPath, FileAccess.Write))
    {
        var writer = XpsDocument.CreateXpsDocumentWriter(xpsDoc);
        writer.Write(chartControl);
    }
}

使用时直接传入你的chart1和输出路径就行,导出的XPS文件可以用系统自带的XPS Viewer打开,也能轻松转换成其他矢量格式(比如PDF)。

方案二:导出为WMF矢量文件(需要处理VisualTree)

如果你一定要WMF格式,那就需要遍历UserControl的Visual树,把每个WPF元素转换成对应的GDI+绘制命令。下面是一个简化版的实现思路:

1. 先确保控件完成布局

和XPS方案一样,导出前必须让控件完成布局:

chart1.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
chart1.Arrange(new Rect(chart1.DesiredSize));
chart1.UpdateLayout();

2. 编写VisualTree遍历与GDI+绘制方法

这个方法会递归遍历UserControl里的所有子元素,把常见的Shape、TextBlock转换成GDI+的绘制操作:

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

private void DrawVisualToGraphics(Graphics g, Visual visual)
{
    int childCount = VisualTreeHelper.GetChildrenCount(visual);
    for (int i = 0; i < childCount; i++)
    {
        var childVisual = VisualTreeHelper.GetChild(visual, i);

        // 处理Shape类元素
        if (childVisual is Shape shape)
        {
            // 转换WPF画笔为GDI+画笔/画刷
            Pen pen = null;
            Brush brush = null;

            if (shape.Stroke != null && shape.Stroke is SolidColorBrush strokeBrush)
            {
                var color = Color.FromArgb(
                    strokeBrush.Color.A,
                    strokeBrush.Color.R,
                    strokeBrush.Color.G,
                    strokeBrush.Color.B);
                pen = new Pen(color, (float)shape.StrokeThickness);
            }

            if (shape.Fill != null && shape.Fill is SolidColorBrush fillBrush)
            {
                var color = Color.FromArgb(
                    fillBrush.Color.A,
                    fillBrush.Color.R,
                    fillBrush.Color.G,
                    fillBrush.Color.B);
                brush = new SolidBrush(color);
            }

            // 根据Shape类型绘制
            if (shape is Line line)
            {
                g.DrawLine(pen, (float)line.X1, (float)line.Y1, (float)line.X2, (float)line.Y2);
            }
            else if (shape is Rectangle rect)
            {
                var rectF = new RectangleF((float)rect.Rect.X, (float)rect.Rect.Y, (float)rect.Rect.Width, (float)rect.Rect.Height);
                if (brush != null) g.FillRectangle(brush, rectF);
                if (pen != null) g.DrawRectangle(pen, rectF.X, rectF.Y, rectF.Width, rectF.Height);
            }
            else if (shape is Ellipse ellipse)
            {
                var rectF = new RectangleF((float)ellipse.Rect.X, (float)ellipse.Rect.Y, (float)ellipse.Rect.Width, (float)ellipse.Rect.Height);
                if (brush != null) g.FillEllipse(brush, rectF);
                if (pen != null) g.DrawEllipse(pen, rectF.X, rectF.Y, rectF.Width, rectF.Height);
            }

            // 释放资源
            pen?.Dispose();
            brush?.Dispose();
        }
        // 处理TextBlock文本
        else if (childVisual is TextBlock textBlock)
        {
            var font = new Font(textBlock.FontFamily.Source, (float)textBlock.FontSize);
            var textBrush = new SolidBrush(Color.FromArgb(
                textBlock.Foreground.Color.A,
                textBlock.Foreground.Color.R,
                textBlock.Foreground.Color.G,
                textBlock.Foreground.Color.B));
            g.DrawString(textBlock.Text, font, textBrush, (float)textBlock.Margin.Left, (float)textBlock.Margin.Top);

            font.Dispose();
            textBrush.Dispose();
        }

        // 递归处理子元素
        DrawVisualToGraphics(g, childVisual);
    }
}

3. 生成WMF文件

调用上面的方法,把UserControl的内容绘制到Metafile里:

public void ExportChartAsWmf(UserControl chartControl, string outputPath)
{
    // 先完成布局
    chartControl.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    chartControl.Arrange(new Rect(chartControl.DesiredSize));
    chartControl.UpdateLayout();

    using (var tempBmp = new Bitmap(1, 1))
    using (var g = Graphics.FromImage(tempBmp))
    {
        var hdc = g.GetHdc();
        var rect = new RectangleF(0, 0, (float)chartControl.ActualWidth, (float)chartControl.ActualHeight);
        
        using (var mf = new Metafile(outputPath, hdc, rect, MetafileFrameUnit.Pixel))
        using (var mfGraphics = Graphics.FromImage(mf))
        {
            // 匹配WPF的96DPI
            float scale = mfGraphics.DpiX / 96f;
            mfGraphics.ScaleTransform(scale, scale);
            
            // 绘制控件内容
            DrawVisualToGraphics(mfGraphics, chartControl);
        }
        
        g.ReleaseHdc(hdc);
    }
}

为什么之前的Canvas方法没用?

你把UserControl加到Canvas里但没效果,是因为VisualHost只是包装了Visual,但Canvas不会自动帮UserControl完成布局。你需要手动调用Measure和Arrange给VisualHost设置尺寸,才能让UserControl正确显示:

private void InterceptChart() 
{
    var visualHost = new VisualHost { Visual=chart1};
    // 给VisualHost设置尺寸
    chart1.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
    visualHost.Width = chart1.DesiredSize.Width;
    visualHost.Height = chart1.DesiredSize.Height;
    visualHost.Arrange(new Rect(visualHost.Width, visualHost.Height));
    TChartCanvas.Children.Add(visualHost); 
}

不过这种方法还是需要你的WPFPainter能处理VisualHost里的Visual内容,不如直接用上面的XPS或WMF方案更可靠。

内容的提问来源于stack exchange,提问作者UabasIerdna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:14:54