While part of the Steema team was at the Mobile World Congress and WIPJam events in Barcelona, getting acquainted with the novelties on the mobile sector, some of us remained at the office in Girona working on some vintage stuff, let’s call it.
Over the years, one of the recurring questions with TeeChart Pro VCL/FMX has been how to create a transparent chart. We have an old Delphi demo project which accomplishes this. It consists of an image in a form and a chart over it. The goal is to make the chart transparent so that the image can be seen through the chart background. This is achieved by first making the chart back wall transparent and then, generating a bitmap the size of the chart from the background image at the chart location and drawing it on the TChart canvas. This process produces a chart like that:
which still is an interactive chart which responds to mouse action: clicks, zoom, panning, etc.
Pretty simple in Delphi, huh? Now let’s complicate things a little bit. We were faced with the question of how to do the same with TeeChart ActiveX. Actually, I don’t know why this didn’t come up before or, if it had been asked for, I was not aware of it. Anyway, this wouldn’t have sounded that complicated if it hadnn’t been because it ended up being a Frankenstein project, since it needed to be TeeChart Pro ActiveX in VB.NET. So a nice COM/.NET mix. Well, this may not make a Frankenstein but wait, the sophistication doesn’t end here. As you may already know, TeeChart Pro ActiveX is a COM wrapper of the TeeChart Pro VCL/FMX version, so an intriguing mixture of Delphi (VCL) code with ActiveX objects and .NET methods/properties. It doesn’t sound that straightforward now, does it?
Ok, let’s break things into different parts and will see how the original Delphi code was
literally ported to VB with TeeChart ActiveX. First of all, setting the chart panel to be transparent gets somewhat complicated when mixing ActiveX and .NET worlds:
AxTChart1.Panel.Color = Convert.ToUInt32(ColorTranslator.ToOle(Color.Transparent))
That tricky conversion is the only remarkable part of initial chart settings. The substantial code is in the OnBeforeDrawChart event though. That’s how it looks like in Delphi:
procedure TForm1.Chart1BeforeDrawChart(Sender: TObject); begin if not Assigned(Back) then begin Back:=TBitmap.Create; Back.Width:=Chart1.Width; Back.Height:=Chart1.Height; Back.Canvas.CopyRect(Chart1.ClientRect,Canvas,Chart1.BoundsRect); end; if Chart1.Color=clNone then Chart1.Canvas.Draw(0,0,Back); end;
All that fuss for about 10 lines of code!? Well, first I should admit that Steema’s .NET language of choice is C#. So I have some difficulties converting C# to VB. Luckily, most of them are solved using Carlos Aguilar’s VB to (and from) C# code translator. You’ll also notice that I’m not an expert on code formatting in WordPress either. I must admit this is my very first article and found that Posting Source Code suggested solution doesn’t work very well for me. I hate poorly indented code so any help on this will be appreciated.
Ok, back on track, I needed to find out which is the equivalent method of Delphi’s CopyRect, which basically copies a part of an image into another image canvas. This can be done with System.Drawing.Graphics.DrawImage method. It has several overloads so I had to find out the one that does the same as CopyRect in Delphi. The most similar overload I could find is this. With a little help from a search engine I found that that simple Delphi call would turn out to be another 10 line method in VB:
Private Function CopyRect(ByVal srcBitmap As Bitmap, _ ByVal destRect As Rectangle, ByVal srcRect As Rectangle) As Bitmap ' Create the new bitmap and associated graphics object Dim bmp As New Bitmap(srcRect.Width, srcRect.Height) Dim g As Graphics = Graphics.FromImage(bmp) 'Draws the specified portion of the specified Image at the specified location and with the specified size. g.DrawImage(srcBitmap, destRect, srcRect, GraphicsUnit.Pixel) ' Clean up g.Dispose() ' Return the bitmap Return bmp End Function
It would look like I was halfway done but I found that I was completely wrong. ActiveX controls don’t have ClientRect property that Delphi controls have. I also had to manually create BoundsRect. Nothing complicated but something the almighty Delphi also did for me:
Dim ClientRect As Rectangle = New Rectangle(0, 0, AxTChart1.Width, AxTChart1.Height) Dim ChartBounds As Rectangle = New Rectangle(AxTChart1.Location.X, AxTChart1.Location.Y, _ AxTChart1.Width, AxTChart1.Height)
So now that all the elements are in place, I just needed to paint the resulting bitmap to TeeChart’s canvas. But wait, another ActiveX vs .NET trick was still waiting for me. Calling Canvas.Draw on TeeChart ActiveX with a .NET Framework native System.Drawing.Bitmap was showing a warning about an image format conversion issue. Besides of that, I decided to go ahead but the warning turned to a run-time error. I had to convert the .NET bitmap to a stdole.IPictureDisp. Thanks to this article I learned that I had to create an additional class derived from AxHost to have access to some private methods of this class that would do the conversion for me. I copied the class, converted it to VB with the mentioned code translator and I was all set to paint the image into TeeChart’s canvas:
If AxTChart1.Panel.Color = Convert.ToUInt32(ColorTranslator.ToOle(Color.Transparent)) Then Dim backImage As stdole.IPictureDisp = AxHostConverter.ImageToPictureDisp(Back) AxTChart1.Canvas.Draw(0, 0, backImage) End If
Now all code was complete and I could run and see the result. I went for it and, to my deception, I found that the image from the picture box was always in the original size; it didn’t come out with the stretching method I was using:
PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage
Once again, Delphi handled this nicely without having to implement any additional code. So, time to scratch my head a little bit more and thanks to internet, I found that I had to create an intermediate image with the stretched image dimensions which resulted in this method:
Private Function GetStretchedImage(ByVal image As Image) As Bitmap If PictureBox1.SizeMode = PictureBoxSizeMode.StretchImage Then Dim bmp As Bitmap = New Bitmap(PictureBox1.Width, PictureBox1.Height) Dim g As Graphics = Graphics.FromImage(bmp) g.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor g.DrawImage(image, New Rectangle(Point.Empty, bmp.Size)) Return bmp Else Return image End If End Function
Phew! This finally produced the result I expected and what was so simple to do in Delphi:
Didn’t I tell you it was some sort of Frankenstein example? If you are interested in seeing all the nuances in detail you can download the complete project. You’ll need TeeChart Pro ActiveX 2014 to run it. A fully functional evaluation version can be download at the TeeChart ActiveX downloads page.