Create a new MFC project with a Chart in Visual Studio 2010 and up

I wrote a quite detailed guide about how I created an MFC project with TeeChart v2014.0.0.1.
Here the step-by-step instructions:

 

– Create a new MFC project:

2014-07-09_1416

2014-07-09_1417

– Note I used “Dialog based” application type to make a simple project:

2014-07-09_1418

– Here it is the blank dialog we get:

2014-07-09_1419

– Drag a “TeeChart Pro ActiveX control v*”:

2014-07-09_1420

-Then we’ll have to create a variable to access the chart at runtime:

2014-07-09_1420_001

– Set a name to it (ie mChart1):

2014-07-09_1423

This action has created tchart1.h and tchart1.cpp in our solutuion.
However, if we build this project now, we get a few errors we need to fix.
The first three errors:

error C2365: ‘pmwNormal’ : redefinition; previous definition was ‘enumerator’ .\mfcaxtest\mfcaxtest\tchart1.h 1406 1 MFCAXTest
error C2365: ‘pmwInverted’ : redefinition; previous definition was ‘enumerator’ .\mfcaxtest\mfcaxtest\tchart1.h 1407 1 MFCAXTest
error C2365: ‘pmwNone’ : redefinition; previous definition was ‘enumerator’ .\mfcaxtest\mfcaxtest\tchart1.h 1409 1 MFCAXTest

We can fix them changing some defines at tchart1.h, where it says this:

1
2
3
4
5
6
enum
{
pmwNormal = 0,
pmwInverted = 1,
pmwNone = 2
}EMouseWheelStyle;
enum
{
pmwNormal = 0,
pmwInverted = 1,
pmwNone = 2
}EMouseWheelStyle;

 

Change it for this:

1
2
3
4
5
6
enum
{
pmwsNormal = 0,
pmwsInverted = 1,
pmwsNone = 2
}EMouseWheelStyle;
enum
{
pmwsNormal = 0,
pmwsInverted = 1,
pmwsNone = 2
}EMouseWheelStyle;

 

This error:

error C2365: ‘tsNone’ : redefinition; previous definition was ‘enumerator’ .\mfcaxtest\mfcaxtest\tchart1.h 1525 1 MFCAXTest

We can fix it changing some other defines at tchart1.h, where it says this:

1
2
3
4
5
6
enum
{
tsNone = 0,
tsStacked = 1,
tsStacked100 = 2
}ETowerStacked;
enum
{
tsNone = 0,
tsStacked = 1,
tsStacked100 = 2
}ETowerStacked;

 

Change it for this:

1
2
3
4
5
6
enum
{
twsNone = 0,
twsStacked = 1,
twsStacked100 = 2
}ETowerStacked;
enum
{
twsNone = 0,
twsStacked = 1,
twsStacked100 = 2
}ETowerStacked;

 

And this one:

error C2664: ‘CTchart1::CTchart1(const CTchart1 &)’ : cannot convert parameter 1 from ‘int’ to ‘const CTchart1 &’ .\mfcaxtest\mfcaxtest\mfcaxtestdlg.cpp 54 1 MFCAXTest

We can fix it removing the 0 argument at MFCAXTestDlg.cpp, where it says this:

1
2
3
CMFCAXTestDlg::CMFCAXTestDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CMFCAXTestDlg::IDD, pParent)
, mChart1(0)
CMFCAXTestDlg::CMFCAXTestDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CMFCAXTestDlg::IDD, pParent)
, mChart1(0)

 

Change it for this:

1
2
3
CMFCAXTestDlg::CMFCAXTestDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CMFCAXTestDlg::IDD, pParent)
, mChart1()
CMFCAXTestDlg::CMFCAXTestDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CMFCAXTestDlg::IDD, pParent)
, mChart1()

 

– Now, just to make things a bit more clean (so this is optional), add this to the “protected” section at MFCAXTestDlg.h:

1
void initChart();
void initChart();

 

Add this at the end of MFCAXTestDlg.cpp:

1
2
3
4
void CMFCAXTestDlg::initChart()
{
 
}
void CMFCAXTestDlg::initChart()
{

}

 

And call initChart at the end of OnInitDialog, still at MFXAXTestDlg.cpp:

1
2
3
4
5
6
7
8
9
10
BOOL CMFCAXTestDlg::OnInitDialog()
{
//...
 
// TODO: Add extra initialization here
 
initChart();
 
return TRUE; // return TRUE unless you set the focus to a control
}
BOOL CMFCAXTestDlg::OnInitDialog()
{
//...

// TODO: Add extra initialization here

initChart();

return TRUE; // return TRUE unless you set the focus to a control
}

 

At this point the project builds fine, and we have the mChart1 variable but this variable doesn’t give us access to the chart properties and methods.

 

– Open the “Class Wizzard”:

2014-07-09_1624

– Select the “MFC Class From ActiveX Control…” option in the Add submenu:

2014-07-09_1621

– Select “TeeChart Pro ActiveX control v*” in the combobox and move “ITChart” to the “Generated classes” box:

2014-07-09_1622

This creates CTChart.h and CTChart.cpp. Now, at MFCAXTestDlg.h, change the include:

1
#include "tchart1.h"
#include "tchart1.h"

 

For this:

1
#include "CTChart.h"
#include "CTChart.h"

And also change mChart1 declaration:

1
CTchart1 mChart1;
CTchart1 mChart1;

 

For this:

1
CTChart mChart1;
CTChart mChart1;

 

– Now you should be able to see the chart methods and properties for mChart1. Ie, at MFCAXTestDlg.cpp:

1
2
3
4
void CMFCAXTestDlg::initChart()
{
mChart1.AboutBox();
}
void CMFCAXTestDlg::initChart()
{
mChart1.AboutBox();
}

 

But you only see the properties and methods at the chart level. To get access to the other classes, we still have to create and include the other required headers.

 

– Open again the “Class Wizard”. And this time select the “MFC Class From TypeLib…” option in the Add submenu:

2014-07-09_1432

– Move all the interfaces to the “Generated classes” (>> button):

2014-07-09_1432_001

Now a lot of headers have been added to the solution. But they include this line that will give us more problems:

1
#import "C:\\Program Files (x86)\\Steema Software\\TeeChart Pro v2014 ActiveX Control\\TeeChart2014.ocx" no_namespace
#import "C:\\Program Files (x86)\\Steema Software\\TeeChart Pro v2014 ActiveX Control\\TeeChart2014.ocx" no_namespace

 

So we have to use the “Quick Replace” function in Visual Studio (Ctrl+H) to replace the line above for this on the entire solution to remove it:

1
//#import "C:\\Program Files (x86)\\Steema Software\\TeeChart Pro v2014 ActiveX Control\\TeeChart2014.ocx" no_namespace
//#import "C:\\Program Files (x86)\\Steema Software\\TeeChart Pro v2014 ActiveX Control\\TeeChart2014.ocx" no_namespace

 

Now we use write the code we want for the chart initialization on MFCAXTestDlg.cpp. Ie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//...
#include "CTitles.h"
#include "CAspect.h"
#include "CLegend.h"
#include "CSeries.h"
#include "CColorGridSeries.h"
#include "CPen0.h"</code>
 
//...
 
void CMFCAXTestDlg::initChart()
{
    CTitles header = mChart1.get_Header();
    header.put_Caption(mChart1.get_Version());
 
    CAspect a = mChart1.get_Aspect();
    a.put_View3D(false);
 
    CLegend l = mChart1.get_Legend();
    l.put_Visible(false);
 
    mChart1.AddSeries(30);
    CSeries s = mChart1.Series(0);
    s.FillSampleValues(100);
    CColorGridSeries cgs = s.get_asColorGrid();
    CPen0 cgsPen = cgs.get_Pen();
    cgsPen.put_Visible(false);
}
//...
#include "CTitles.h"
#include "CAspect.h"
#include "CLegend.h"
#include "CSeries.h"
#include "CColorGridSeries.h"
#include "CPen0.h"</code>

//...

void CMFCAXTestDlg::initChart()
{
	CTitles header = mChart1.get_Header();
	header.put_Caption(mChart1.get_Version());

	CAspect a = mChart1.get_Aspect();
	a.put_View3D(false);

	CLegend l = mChart1.get_Legend();
	l.put_Visible(false);

	mChart1.AddSeries(30);
	CSeries s = mChart1.Series(0);
	s.FillSampleValues(100);
	CColorGridSeries cgs = s.get_asColorGrid();
	CPen0 cgsPen = cgs.get_Pen();
	cgsPen.put_Visible(false);
}

Converting VCL/FMX and ActiveX templates to .NET.

Over the years, a number of TeeChart users have asked how to convert the charts they created either using TeeChart VCL/FMX or ActiveX to the .NET version, enabling them to more easily port their previously created charting projects to .NET.

Well, this is possible! It might not be the ideal or perfect solution but it’s an approximation that can save you some work. This can be achieved in two ways:

  1. Using the TeeToTen application. It is a .NET application that uses TeeChart ActiveX to load the .tee files (TeeChart VCL/FMX and ActiveX templates), convert them to text files, generate an XML file with series and data and then load them into a .NET chart  which is then used to generate the .ten file (TeeChart .NET templates) file. The tool comes with a readme.txt document that explains which are its prerequisites and how to use it. TeeToTen tool can also be called via command line with several parameter options. This way it can be called from your applications to obtain a completely automatic conversion. Full details on how to use it are available at included readme.txt.
  2. This solution by-passes the ActiveX version and uses TeeToText, a small VCL application that loads .tee files and generates the necessary text and XML files. Actually, anybody that uses TeeChart VCL/FMX or TeeChart ActiveX can easily generate such files using their built in exporting functionalty, which is what TeeToText does. Once the process is complete,  you need to use TenCreator.dll included with TeeToTen  to import these generated files into your .NET chart. Here’s an example of TenCreator.dll being used to convert one file:
1
2
3
4
5
6
7
8
string chartFile = @"C:\temp\TemplateSamples\Annotations.txt";
string dataFile = @"C:\temp\TemplateSamples\Annotations.xml";
 
TenCreator.TenStreamer streamer = new TenCreator.TenStreamer();
System.IO.Stream netStream = streamer.ConvertFile(chartFile, dataFile);
netStream.Position = 0;
tChart1.Import.Template.Load(netStream);
tChart1.Export.Template.Save(@"C:\TemplateSamples\Annotations.ten");
string chartFile = @"C:\temp\TemplateSamples\Annotations.txt";
string dataFile = @"C:\temp\TemplateSamples\Annotations.xml";

TenCreator.TenStreamer streamer = new TenCreator.TenStreamer();
System.IO.Stream netStream = streamer.ConvertFile(chartFile, dataFile);
netStream.Position = 0;
tChart1.Import.Template.Load(netStream);
tChart1.Export.Template.Save(@"C:\TemplateSamples\Annotations.ten");

and here’s an example converting a complete folder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public Form1()
{
InitializeComponent();
InitializeChart();
}
 
private void InitializeChart()
{
DirectoryInfo dFolder = new DirectoryInfo(@"C:\TemplateSamples\");
SearchOption so = new SearchOption();
bool incSubDirectories = false;
 
if (incSubDirectories)
{
so = SearchOption.AllDirectories;
}
else
{
so = SearchOption.TopDirectoryOnly;
}
 
FileInfo[] fFileArray = dFolder.GetFiles("*.tee", so);
 
foreach (FileInfo fFile in fFileArray)
{
ConvertFile(fFile.FullName);
}
}
 
private void ConvertFile(string fileName)
{
string chartFile = fileName.Replace(".tee", ".txt");
string dataFile = fileName.Replace(".tee", ".xml");
 
TenCreator.TenStreamer streamer = new TenCreator.TenStreamer();
Stream netStream = streamer.ConvertFile(chartFile, dataFile);
netStream.Position = 0;
tChart1.Import.Template.Load(netStream);
tChart1.Export.Template.Save(fileName.Replace(".tee", ".ten"));
}
public Form1()
{
InitializeComponent();
InitializeChart();
}

private void InitializeChart()
{
DirectoryInfo dFolder = new DirectoryInfo(@"C:\TemplateSamples\");
SearchOption so = new SearchOption();
bool incSubDirectories = false;

if (incSubDirectories)
{
so = SearchOption.AllDirectories;
}
else
{
so = SearchOption.TopDirectoryOnly;
}

FileInfo[] fFileArray = dFolder.GetFiles("*.tee", so);

foreach (FileInfo fFile in fFileArray)
{
ConvertFile(fFile.FullName);
}
}

private void ConvertFile(string fileName)
{
string chartFile = fileName.Replace(".tee", ".txt");
string dataFile = fileName.Replace(".tee", ".xml");

TenCreator.TenStreamer streamer = new TenCreator.TenStreamer();
Stream netStream = streamer.ConvertFile(chartFile, dataFile);
netStream.Position = 0;
tChart1.Import.Template.Load(netStream);
tChart1.Export.Template.Save(fileName.Replace(".tee", ".ten"));
}

This project is a work in progress. It’s being improved upon user demand so feel free to let us know your feedback at info at steema dot com.  We hope this helps in the transition of your existing projects that use TeeChart to the .NET platform.

 

How to make a transparent chart with TeeChart Pro ActiveX

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:

Chart with a transparent background in Delphi.
Chart with a transparent background in Delphi.

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:

TransparentActiveXNET

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.