Save an image from MSChart Control
Level: Intermediate
Save an image from MSChart Control
Sometimes, we need to save an image from our chart that we are showing in our application. Unfortunatelly, MSChart control doesn't allow us to do it like Excel Chart.
That's a pity but with a little API calls, i will show you not one but two ways to achieve this.
Plan "A" _______________________________________
Using BitBlt API
MSChart control doesn't exposes its hwnd property like other controls in Visual Basic (as PictureBox, textBox, the form itself, and so on) which is what we need to control some things from the control to get final results.
Create a new Standard Exe Project and add a MSChart control, A Command Button and a PictureBox control. The data in the chart is not important here, leave the default.
So, the first thing to know is its hwnd value. To do that we will take some API stuff. See the following code (put it in a separate module):
'modEnumChildWindows.bas
Option Explicit
Public Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Public gHwnd As Long
Public strClassName As String
Public Function EnumChildProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
Dim sSave As String, ret As Long
Static i As Integer
sSave = String$(255, Chr$(0))
Get the name of the target class
ret = GetClassName(hWnd, sSave, 255)
'remove the last Chr$(0)
sSave = Left$(sSave, ret)
'
Debug.Print Chr$(34) & sSave & Chr$(34); i
i = i + 1
If sSave = strClassName Then gHwnd = hWnd
'continue enumeration
EnumChildProc = 1
End Function
That's the code we need to find the hwnd of our Chart (More on this later) and it could be use to find the hwnd for almost any child window of our form, in case that the control doesn't exposes one.
This callback function enumerates all child windows that belongs to our target form, check the class name, if it matchs with our class name, populates the gHwnd variable with its hwnd value.
At General declarations section of our form, paste this API stuff:Option Explicit
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
At Form_Load event of our main form, we could paste this code:
Private Sub Form_Load()
'This is the class name of our target window
strClassName = "MSChart20WndClass"
'Enumerate child windows of the form
EnumChildWindows Me.hWnd, AddressOf EnumChildProc, 0
End Sub
Note:I will not describe here how to use AddressOf function, you could get it from MSDN or at Internet.
The "real" action will take place at Form1_Click event:Private Sub Form_Click()
Me.AutoRedraw = True
With Picture1
.Width = MSChart1.Width
.Height = MSChart1.Height
BitBlt .hDC, 0, 0, .Width, .Height, GetWindowDC(hwndChart), 0, 0, vbSrcCopy
SavePicture .Image, "c:\mychart.bmp"
End With
End Sub
Instead of use Form1_Click event, we could encapsulate the code inside a sub/function with the name of the resulting bmp as parameter.
The last step is go to Explorer window and do a double-click in c:\mychart.bmp to see the results.
Plan "B" _______________________________________
Using Allapi.net code
This code Not belongs to me, i only did little changes to meets the requirements.
General declarations section of form:Option Explicit
Const RC_PALETTE As Long = &H100
Const SIZEPALETTE As Long = 104
Const RASTERCAPS As Long = 38
Private Type PALETTEENTRY
peRed As Byte
peGreen As Byte
peBlue As Byte
peFlags As Byte
End Type
Private Type LOGPALETTE
palVersion As Integer
palNumEntries As Integer
palPalEntry(255) As PALETTEENTRY ' Enough for 256 colors
End Type
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type PicBmp
Size As Long
Type As Long
hBmp As Long
hPal As Long
Reserved As Long
End Type
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As PicBmp, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal iCapabilitiy As Long) As Long
Private Declare Function GetSystemPaletteEntries Lib "gdi32" (ByVal hdc As Long, ByVal wStartIndex As Long, ByVal wNumEntries As Long, lpPaletteEntries As PALETTEENTRY) As Long
Private Declare Function CreatePalette Lib "gdi32" (lpLogPalette As LOGPALETTE) As Long
Private Declare Function SelectPalette Lib "gdi32" (ByVal hdc As Long, ByVal hPalette As Long, ByVal bForceBackground As Long) As Long
Private Declare Function RealizePalette Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Command1_Click event:Private Sub Command1_Click()
'KPD-Team 1999
'URL: http://www.allapi.net/
'E-Mail: KPDTeam@Allapi.net
'Create a picture object from the screen
With MSChart1
Picture1.ScaleWidth = .Width
Picture1.ScaleHeight = .Height
Set Picture1.Picture = hDCToPicture(GetDC(gHwnd), 0, 0, .Width, .Height)
End With
End Sub
We STILL need the EnumChildWindows API to get it work, since MSChart doesn't exposes its hwnd property as stated earlier. Due to that, code from "Plan A"'s Form1_Load event has to be used too and we have to add the SavePicture function to save the graph. The only diference is that you don't need .Image property, you could use .Picture directly since, now, it is a "real" picture object.
Hope you have enjoyed that work as i did.
;)
©2003 - Richie Simonetti