Beschreibung:
Hallo zusammen, ich habe keine Frage sondern will euch meinen VB.net-Code vorstellen, da ich sicher bin, dass er auch einmal von anderen Entwicklern gebraucht wird.
Ich habe eine Möglichkeit gefunden, Diagramme die mit Hilfe der Zedgraph-opensource-libary (https://sourceforge.net/projects/zedgraph/) erstellt wurden mit Hilfe des vektorbasierten Microsoft Formats "Enhanced Metafile (EMF)" bzw. "Windows Metafile (WMF)" in eine PDF zu exportieren. Dabei wird die itextsharp-opensouce-libary (https://sourceforge.net/projects/itextsharp/) verwendet. Ich arbeite zwar mit VB.net aber benutze ja C# Bibiotheken, deshalb habe ich mir dieses Forum ausgesucht, um mit euch meinen Code zu teilen.
Bisher habe ich Zedgraph-Diagramme immer erst als Bitmap exportiert:
Dim bmp As New System.Drawing.Bitmap(1, 1)
bmp = zedGraphControl1.GraphPane.GetImage
bmp.Save("C:\img_" & zedGraphControl1.Name & ".bmp")
und anschließend habe ich das Bitmap in die PDF wie folgt eingebunden,
Imports ZedGraph
Imports iTextSharp.text
Imports iTextSharp.text.pdf
...
Private Sub Bmp2Pdf()
Dim bmp_iText As iTextSharp.text.Image = _
Image.GetInstance("C:\img_" & zedGraphControl1.Name & ".bmp")
'Create a pdf with itextsharp-libary:
Dim pdfdoc As New iTextSharp.text.Document
Dim writer As iTextSharp.text.pdf.PdfWriter
Dim pdfPath As String = "C:\test.pdf"
'Prepare Filestream with UserDef-Path
Dim fs As IO.FileStream = New IO.FileStream(pdfPath, IO.FileMode.Create)
'Prepare instance of PDF to write into it
writer = iTextSharp.text.pdf.PdfWriter.GetInstance(pdfdoc, fs)
pdfdoc.Add(bmp_iText)
End Sub
Aber in diesem Fall kann ich das ZedGraph Diagramm nur als Pixelgrafik betrachten, was sehr unschön ist. Zur Lösung des Problems muss zunächst die "Zedgraph.MasterPane" als Enhanced Metafile (EMF) exportiert werden:
Private Sub ZedGraph2Emf()
'Built Metafile from Graph
Dim emfTempFileFullPath As String = "C:\img_" & zedGraphControl1.Name & ".emf"
Using g As Graphics = Me.CreateGraphics()
Dim hdc As IntPtr = g.GetHdc
Dim metafile As System.Drawing.Imaging.Metafile = _
New System.Drawing.Imaging.Metafile(emfTempFileFullPath, hdc, _
System.Drawing.Imaging.EmfType.EmfPlusDual)
Using gMeta As Graphics = Graphics.FromImage(metafile)
zedGraphControl1.MasterPane.Draw(gMeta) 'Draw ZGraph in EMF
End Using
'Destroy object
metafile.Dispose()
End Using
End Sub
Mit der itextsharp-libary kann leider kein EMF-File in eine PDF eingebunden werden, aber zum Glück das "altmodische" Windows Meta File (WMF)-Format. Deshalb ist es nun notwendig, die erzeugte EMF in eine WMF umzuwandeln. Dies kann durch die von Windows bereitgestellten API GDI-Funktionen realisiert werden. Hierfür habe ich folgende Funktion entwickelt:
Public Function emf2wmf(ByVal emfTempFileFullPath As String, _
ByVal wmfTempFileFullPath As String) As Boolean
'Check existence of EMF-File
If Not IO.File.Exists(emfTempFileFullPath) Then
MsgBox("EMF-File does not exist.", MsgBoxStyle.Exclamation)
Return False
End If
'Construct a Metafile object from an existing EMF disk file
Using fileStreamEmf As IO.FileStream = _
New IO.FileStream(emfTempFileFullPath,IO.FileMode.Open)
Using streamReaderEmf As IO.StreamReader = _
New IO.StreamReader(fileStreamEmf)
Using streamEmf As System.IO.Stream = streamReaderEmf.BaseStream
'Get Metafile
Using mf As New System.Drawing.Imaging.Metafile(streamEmf)
' Get Handle of Metafile
Dim enhMetafileHandle As IntPtr = mf.GetHenhmetafile()
Dim h As IntPtr
'A call to the GetEnhMetaFileBits API-Function (gdiplus.dll,
'part of gdi32.dll) with a null buffer return the size of
'the buffer need to store the WMF bits.
Dim bufferSize As UInteger = _
API.GetEnhMetaFileBits(enhMetafileHandle, 0, h)
'Create an array to hold the bits
Dim buffer(CInt(bufferSize)) As Byte
'Use the GetEnhMetaFileBits API-Function (gdiplus.dll,
'part of gdi32.dll) to convert the Emf to Wmf
API.GdipEmfToWmfBits(enhMetafileHandle, bufferSize, _
buffer, API.MM_ANISOTROPIC, _
API.EmfToWmfBitsFlags.EmfToWmfBitsFlagsIncludePlaceable)
'Save WMF-File with Memory-Stream-Object
Using msMetafileStream As New System.IO.MemoryStream
msMetafileStream.Write(buffer, 0, CInt(bufferSize))
Dim baMetafileData() As Byte
baMetafileData = msMetafileStream.ToArray
'Write Bytes to File, use fileStream
Using fileStreamWmf As System.IO.FileStream = _
New IO.FileStream(wmfTempFileFullPath, _
IO.FileMode.OpenOrCreate, _
IO.FileAccess.ReadWrite, IO.FileShare.None)
For idx As Long = 0 To baMetafileData.Length - 1
fileStreamWmf.WriteByte(baMetafileData(idx))
Next idx
End Using 'fileStream
End Using 'msMetafileStream
End Using 'mf
End Using 'streamEmf
End Using 'streamReaderEmf
End Using 'fileStreamEmf
Return True
End Function
Nach der Konvertierung von EMF zu WMF mit dem Aufruf...
emf2wmf("C:\img_" & zedGraphControl1.Name & ".emf", _
"C:\img_" & zedGraphControl1.Name & ".wmf")
...ist es nun möglich die WMF der PDF hinzuzufügen:
...
Dim wmf As iTextSharp.text.ImgWMF = _
iTextSharp.text.Image.GetInstance("C:\img_" & zedGraphControl1.Name & ".wmf")
pdfdoc.Add(wmf)
Die genutzten GDI-Functions werden über eine erstellte Klasse mit der Bezeichnung Class "API" aufgerufen, die auf dem Beitrag von Convert an image into WMF with .NET? aufbaut und von C# nach VB.Net übersetzt wurde:
Public Class API
Public Enum EmfToWmfBitsFlags
'Use the default conversion
EmfToWmfBitsFlagsDefault = 0
'Embedded the source of the EMF metafiel within the resulting WMF
'metafile
EmfToWmfBitsFlagsEmbedEmf = 1
'Place a 22-byte header in the resulting WMF file. The header is
'required for the metafile to be considered placeable.
EmfToWmfBitsFlagsIncludePlaceable = 2
'Don't simulate clipping by using the XOR operator.
EmfToWmfBitsFlagsNoXORClip = 4
End Enum
'Ensures that the metafile maintains a 1:1 aspect ratio
Public Const MM_ISOTROPIC% = 7
'Allows the x-coordinates and y-coordinates of the metafile to be adjusted
'independently
Public Const MM_ANISOTROPIC% = 8
<System.Runtime.InteropServices.DllImport("gdiplus.dll", EntryPoint:="GdipEmfToWmfBits")> _
Public Shared Function GdipEmfToWmfBits(<System.Runtime.InteropServices.InAttribute()> _
ByVal hEmf As System.IntPtr, ByVal bufferSize As UInteger, ByVal buffer() As Byte, _
ByVal mappingMode As Integer, ByVal flags As EmfToWmfBitsFlags) As UInteger
End Function
<System.Runtime.InteropServices.DllImportAttribute("gdi32.dll", EntryPoint:="GetEnhMetaFileBits")> _
Public Shared Function GetEnhMetaFileBits(<System.Runtime.InteropServices.InAttribute()> _
ByVal hEMF As System.IntPtr, ByVal nSize As UInteger, ByVal lpData As IntPtr) As UInteger
End Function
End Class
Anbei habe ich ein Großteil meines Quellcodes angehängt. (Das komplette Projekt inkl. Bibiothekten wäre zu groß gewesen.)
Ich hoffe das jemand von euch von meinem Beitrag profitieren kann, bzw. ähnliches vorhat und hiermit eine Lösung gefunden hat.
Beste Grüße,
Jonny
PS: Mein Beitrag im ZedGraph-Forum und eine lauffähiges VB.net Beispielprojekt zum downloaden findet sich hier: sourceforge.net: ZedGraph / Discussion / Help:Saving graph as PDF
Schlagwörter: ZedGraph, itextsharp, EMF, WMF, PDF, API, GDI, GDI+,GDLplus, GDI32, emf2wmf, emftowmf, GdipEmfToWmfBits, GetEnhMetaFileBits, ZedGraph in vektorformat, Diagramm als PDF, opensource-Bibiotheken, VB.net
Hallo nochmal,
ich musste die ZedGraph2Emf-Subroutine nochmals ändern, da sonst beim Zoomen das Metafile nicht richtig erstellt wird. Details dazu finden sich hier: sourceforge.net: ZedGraph / Discussion / Help:EMF bug and feature (with code!). Der geänderte Code lautet somit:
Private Sub ZedGraph2Emf()
'Built Metafile from Graph
Dim emfTempFileFullPath As String ="C:\img_" & _
zedGraphControl1.Name & ".emf"
Using g As Graphics = Me.CreateGraphics()
Dim hdc As IntPtr = g.GetHdc
Dim metafile As System.Drawing.Imaging.Metafile = New _
System.Drawing.Imaging.Metafile(emfTempFileFullPath, _
hdc, zedGraphControl1.MasterPane.Rect, _
System.Drawing.Imaging.MetafileFrameUnit.Pixel, _
System.Drawing.Imaging.EmfType.EmfPlusDual)
Using gMeta As Graphics = Graphics.FromImage(metafile)
'Draw ZGraph in EMF
zedGraphControl1.MasterPane.Draw(gMeta)
End Using
'Destroy object
metafile.Dispose()
End Using
End Sub
Gruß Jonny