interface 상속받은 클래스에서 Static 사용하기

인터패이스에 정외 되었고
public interface IDBCon
{
int QueryExecute(string strQuery);
}
 
public class DatabaseMssql: IDBCon
{
public static int QueryExecute(string strQuery)
{
}
}
와 같이 사용하면
“Severity Description Project File Line Suppression StateSeverity Description Project File Line Suppression StateError ‘DatabaseMssql’ does not implement interface member ‘IDBCon.QueryExecute(string)’. ‘DatabaseMssql.QueryExecute(string)’ cannot implement an interface member because it is static. ”
라는 에러가 발생한다
이 에러를 해결하기 위해 아래와 같이 추가 해주면 문제를 해소 시킬 수 있다.
int IDBCon.QueryExecute(string strQuery)
{
return DatabaseMssql.QueryExecute(strQuery);
}
public static int QueryExecute(string strQuery)
{}

C# Access DB 사용

Access 2000-2003 데이타베이스 연결
Access 2000, Access 2002, Access 2003 에서 사용된 .mdb 파일을 사용할 경우 Microsoft Jet OLE DB 4.0 provider를 사용한다. 만약 Admin의 암호를 특별히 설정하지 않았다면, Connection String에서 [User Id=admin;Password=;] 부분을 생략할 수 있다.

string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:   est.mdb;User Id=admin;Password=;"
OleDbConnection conn = new OleDbConnection(connStr);
conn.Open();

Access 2007 이상에서 데이타베이스 연결
Access 2007 버전 이상 (예: Access 2007, 2010, 2013, 2016 등) 에서 사용되는 .accdb 파일을 사용할 경우 Microsoft ACE OLEDB 12.0 provider를 사용한다. (주: Access 버전이 올라감에 따라 더 향상된 Microsoft ACE OLEDB Provider가 지원되는데, Access 2007 버전 이상에서 공통적으로 Microsoft ACE OLEDB 12.0 provider을 지원한다)

string connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:   est.accdb;"
OleDbConnection conn = new OleDbConnection(connStr);
conn.Open();

ADO.NET의 사용 : 테이블 생성과 데이타 추가 예제
엑세스 DB를 OleDbConnection을 사용해 연결을 한 후에는, 다른 데이타 소스들과 같이 OleDb* 클래스들을 이용해 데이타를 가져오거나 삽입, 삭제, 갱신등을 하게 된다. 아래 예제는 테이블을 만들고 데이타를 한 개 추가하는 예이다.

예제

string connStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\test.accdb";
using (OleDbConnection conn = new OleDbConnection(connStr))
{
    conn.Open();
    string sql = "CREATE TABLE MyTable (ID int, Name nvarchar(100))";
    OleDbCommand cmd = new OleDbCommand(sql, conn);
    cmd.ExecuteNonQuery();
    sql = "INSERT INTO MyTable VALUES(1,'Lee')";
    cmd.CommandText = sql;
    cmd.ExecuteNonQuery();
}

ADO.NET의 사용 : 데이타 읽기 및 바인딩
엑세스의 데이타를 가져오기 위해서는 OleDbCommand/OleDbDataReader 혹은 OleDbDataAdapter를 사용한다. OleDbDataReader는 데이타를 한 Row씩 가져오는 반면, OleDbDataAdapter는 한꺼번에 클라이언트 메모리로 데이타를 가져온다. OleDbDataAdapter에서 가져온 데이타는 주로 DataSet 객체 안에 메모리상의 테이블 형태로 존재하는데, 이를 각종 컨트롤들에게 바인딩시킬 수 있다. 예를 들어, 윈폼의 DataGridView 컨트롤, ASP.NET의 GridView 컨트롤, WPF의 DataGrid 컨트롤 등의 그리드 컨트롤에 바인딩시키면, 메모리상의 데이타를 테이블 형태로 표시할 수 있다.

예제

// Fill 전달 전에 DataSet객체 생성
DataSet ds = new DataSet();
// DataAdapter는 자동으로 Connection을
// 핸들링한다. conn.Open() 불필요.
string connStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\test.accdb";
OleDbConnection conn = new OleDbConnection(connStr);
string sql = "SELECT * FROM MyTable";
OleDbDataAdapter adp = new OleDbDataAdapter(sql, conn);
adp.Fill(ds);
// 가져온 데이타를 DataGridView 컨트롤에 
// 바인딩한 예.
dataGridView1.DataSource = ds.Tables[0];
DB생성

public static bool CreateDatabase(string fullFilename)
{
bool succeeded = false;

    try
{
string newDB = “Provider=Microsoft.Jet.OLEDB.4.0;Data Source=” + fullFilename;
Type objClassType = Type.GetTypeFromProgID(“ADOX.Catalog”);

        if (objClassType != null)
{
object obj = Activator.CreateInstance(objClassType);

            // Create MDB file
obj.GetType().InvokeMember(“Create”, System.Reflection.BindingFlags.InvokeMethod, null, obj,
new object[] { “Provider=Microsoft.Jet.OLEDB.4.0;Data Source=” + newDB + “;” });
            succeeded = true;
            // Clean up
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
}

    catch (Exception ex)
{
MessageBox.Show(“Could not create database file: ” + fullFilename + “\n\n” + ex.Message, “Database Creation Error”);
}

    return succeeded;
}

 

2D Graph ActiveX Control in C++ with ATL (No MFC Dependency)

DmGraph_src

Introduction

Quite often, developers need to plot various data. They expect to use a light control with minimal dependencies.

Background

NTGraph control is a powerful ActiveX control which plots multiple data sets. Unfortunately, it depends on MFC libraries.
This new 2D Graph ActiveX control, named DMGraph, is based on the NTGraph drawing engine but eliminates the MFC dependency. For DMGraph, ATL 3.0 was used as the framework. The only dependencies are some Microsoft Windows DLLs (the C runtime library msvcrt.dll is part of the OS starting with Windows 2000). This means there are no deployment issues – DMGraph works on Windows 2000 or later.
Another major change compared with old NTGraphCtrl is the exposed COM interface architecture. Instead of putting everything together under one interface, DMGraphCtrl exposes a hierarchy of interfaces which represents entities used on drawing.

Using the Code

The main interface IDMGraphCtrl contains collections of items (managed by the IDMGraphCollectioninterface). This collection interface exposes the usual methods (such as Add, Delete, Count, Item). What is specific is the concept of “selected item”. One item in the collection can be the “selected” one. Sometimes user operations (such as mouse drag) apply to the “selected” item (if any). The IDMGraphCollection::Selectedproperty gets/sets the index of the selected item.
When the user double clicks the graph area, a modal dialog with property pages is displayed. This dialog may be invoked programmatically as well using the ShowProperties method. Modifying data in these property pages has an immediate effect on the displayed graph.
The CDMGraphCtrl class implements the IDMGraphCtrl interface. At runtime, some properties can be viewed or changed using the DM Graph property page:

The CDMGraphCtrl class keeps the following collections exposed by the IDMGraphCtrl interface:

1. Element Collection

The get_Elements property exposes the elements collection.
Each item is an instance of the CGraphElement class which exposes the IDMGraphElement interface. A graph element is a collection of points which need to be plotted. The graph element has various properties which define its drawing style. For example, the Linetype property defines what kind of line should be used to connect the points (including “Null” – no lines at all). Color, width, shape can be set for points; the entire set of points can be enabled/disabled for drawing, etc. Each graph element is identified by a “name“. All these are accessible through COM properties exposed by the IDMGraphElement interface. When such a property is set, the entire graph is re-drawn to reflect the changes.
The set of points (data to be plotted) is supplied by the client using several methods:

  • Plot – Two one-dimensional arrays with same size (one for X, the other for Y) will set the entire point collection for a specific graph element.
  • PlotXY – Appends just one point to the point collection (both X and Y coordinates are specified).
  • PlotY – Appends just one point to the point collection (only Y is specified, the X is the index of the added point in the points collection).

Each time the point collection is modified, the graph is updated to reflect the changes but the range is not updated. If the new point(s) go(es) out of range, then the SetRange or AutoRange methods need to be called.
New elements can be added to the collection, existing ones removed, selected element index can be changed, and selected element properties can be viewed/changed from the Elements property page.

2. Annotation Collection

The get_Annotations property exposes the annotations collection.
One annotation is a piece of text which is displayed on a specific position on the graph. This collection keeps instances of the CGraphAnnotation class which exposes the IDMGraphAnnotation interface. Using this interface, various properties can be accessed – such as caption (the displayed text), position, color, text orientation, background enable/disable. When such a property is set, the entire graph is re-drawn to reflect the changes.
New annotations can be added to the collection, existing ones removed, selected annotation index can be changed, and selected annotation properties can be viewed/changed from the Annotations property page.

3. Cursor Collection

The get_Cursors property exposes the cursors collection.
A cursor is made of one or two lines which are parallel with the X or Y axis. The IDMGraphCursor interface deals with cursor specific properties. If the Style property (type is the Crosshair enum) is set to “XY” then the cursor will have two lines: one parallel with X axis and the other parallel with Y axis. If the cursor Mode is set to Snap, then the selected cursor will snap to the closest point of the selected graph element during mouse drag.
New cursors can be added to a collection, existing ones removed, selected cursor index can be changed, and selected cursor properties can be viewed/changed from the Cursors property page.

4. Axis Objects

Two objects are exposed by the get_Axis property: one for X (horizontal) axis and the other for Y (vertical) axis. The objects are instances of the CGraphAxis class which exposes the IDMGraphAxis interface. Various properties can be get/set for each axis. If the put_Time property is set to VARIANT_TRUE, then the double values for that axis are considered to be date/time values. These values are interpreted like the DATE type (used in OLE Automation VARIANT union). The values are displayed according to the format string set by the Formatproperty. For date/time, possible format strings are documented in the strftime function in MSDN. Otherwise, for non logarithmic axis, the usual sprintf format strings are accepted. Some axis properties are available in the DM Graph property sheet (see above) while others are available in the Format property page (see below).

From the Axis combo box, the X (bottom) or Y (left) axis can be selected. Then the data type can be set for the selected axis. For each type, the Templates list box is filled with the available format templates. When a template item is selected from the left, the Format string on the right is updated.

Basic Usage

1. From a Windows Client Written in C++ with ATL

class CMainWnd : public CWindowImpl<CMainWnd>
{
     CAxWindow*                 m_pGraphCtrl;
     CComPtr<IDMGraphCtrl>      m_spDMGraph;
};
LRESULT CMainWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
     m_pGraphCtrl = new CAxWindow;
     if(m_pGraphCtrl == NULL)
           return -1;
     if(!AtlAxWinInit())
           return -1;
     HRESULT hr;
     CComPtr<IAxWinHostWindow> spHost;
     hr = m_pGraphCtrl->QueryHost(IID_IAxWinHostWindow, (void**)&spHost);
     if(FAILED(hr))
     {
         Message(hr, NULL, L"Cannot query Ax host");
         return -1;
     }
     hr = spHost->CreateControl(L"DMGraph.DMGraphCtrl", m_pGraphCtrl->m_hWnd, NULL);
     if(FAILED(hr))
     {
         Message(hr, NULL, L"Cannot start DM Graph control");
         return -1;
     }
     CComVariant vData;
     hr = m_pGraphCtrl->QueryControl(IID_IDMGraphCtrl, (void**)&m_spDMGraph);
     if(FAILED(hr) || m_spDMGraph == NULL)
     {
         Message(hr, NULL, L"Cannot query DM Graph control");
         return -1;
     }
     return 0;
}
void CMainWnd::SetGraphData(VARIANT* pvarrX, VARIANT* pvarrY, LPCTSTR szName)
{
  ATLASSERT(pvarrX);
  ATLASSERT(pvarrY);
  ATLASSERT(szName);
  CComBSTR bsName(szName);
  CComPtr<IDMGraphCollection> spElements;
  CComPtr<IDMGraphElement> spGraphElement;
  HRESULT hr = m_spDMGraph->get_Elements(&spElements);
  long i, nElementCount = 0;
  BOOL bReplace = FALSE;
  hr = spElements->get_Count(&nElementCount);
  for(i=0; i<nElementCount; i++)
  {
     CComBSTR bsElemName;
     CComPtr<IDispatch> spDispatch;
     hr = spElements->get_Item(i, &spDispatch);
     hr = spDispatch.QueryInterface(&spGraphElement);
     spGraphElement->get_Name(&bsElemName);
     if(_wcsicmp(bsElemName, bsName) == 0)
    {
         OLECHAR szMsgText[256];
        _snwprintf(szMsgText, 256,
              L"There is ALREADY an element named '%s'.\n"
              L"Do you want to replace it ?", bsElemName);
         if(::MessageBoxW(m_hWnd, szMsgText, NULL,
            MB_YESNO|MB_ICONQUESTION) != IDYES)
         {
              return;
         }
         bReplace = TRUE;
         break;
    }
    else
         spGraphElement = NULL;
  }
  if(bReplace == FALSE || spGraphElement == NULL)
  {
     CComPtr<IDispatch>     spDispatch;
     hr = spElements->Add(&spDispatch);
     spGraphElement = NULL;
     hr = spDispatch.QueryInterface(&spGraphElement);
  }
  hr = spGraphElement->put_Name(bsName);
  hr = spGraphElement->put_PointSymbol( Dots );
  hr = spGraphElement->put_PointSize(3);
  hr = spGraphElement->Plot(*pvarrX, *pvarrY);
  if(FAILED(hr))
  {
     Message(hr, spGraphElement, L"Failed to plot items");
     return;
  }
  hr = m_spDMGraph->AutoRange();
}

2. From an HTML Page using VBScript

Inside the HTML body, the ActiveX is created using the object tag. A button click will execute a script to set the data to be plotted.

<object ID="DMGraphCtrl"
      CLASSID="CLSID:AAF89A51-7FC0-43B0-9F81-FFEFF6A8DB43"
      width=600 height=400 VIEWASTEXT></object>
<input id=BtnSin value=sin type="button">
<script id=clientEventHandlersVBS language="vbscript">
<!--
Sub BtnSin_onclick
  On Error Resume Next
  Dim dmGraphCtrl
  Set dmGraphCtrl = document.getElementById("DMGraphCtrl")
  Dim idx : idx = dmGraphCtrl.Elements.Selected
  If idx < 0 Then
    MsgBox("Error: please create and select an element first." &_
             vbCrLf & "(Double click to see property pages)")
  Else
    Dim selElement
    Set selElement = dmGraphCtrl.Elements.Item(idx)
    Dim i
    Dim x()
    Dim y()
    ReDim x(100)
    ReDim y(100)
    For i=0 To 100
      x(i) = i/5
      y(i) = Sin( x(i) )
    Next
    selElement.Plot x, y
    dmGraphCtrl.AutoRange()
  End If
  If Err.number <> 0 Then
    MsgBox Err.Description
  End If
End Sub
-->
</script>

3. From a Windows Client Written in C++ with MFC

#import "..\DMGraph\DMGraph.tlb" no_namespace raw_interfaces_only

class CDmGraphMfcClientDlg : public CDialog
{
    ... ... ...
    IDMGraphCtrlPtr m_spGraph;
};
BOOL CDmGraphMfcClientDlg::OnInitDialog()
{
    ... ... ...
    CWnd* pwndCtrl = GetDlgItem(IDC_DMGRAPHCTRL1);
    ASSERT_VALID(pwndCtrl);
    IUnknown* pUnkCtrl = pwndCtrl->GetControlUnknown(); //weak reference
    HRESULT hr;
    m_spGraph = pUnkCtrl;
    IDMGraphCollectionPtr colElements;
    hr = m_spGraph->get_Elements(&colElements);
    IDispatchPtr spDisp;
    IDMGraphElementPtr spElem;
    hr = colElements->Add(&spDisp);
    spElem = spDisp;
    hr = spElem->put_Name(_bstr_t("sin"));
    hr = spElem->put_PointSymbol( Dots );
    hr = spElem->put_PointSize(1);
    hr = spElem->put_PointColor( RGB(255, 0, 0) );
    COleSafeArray arrx, arry;
    arrx.CreateOneDim(VT_R8, 100);
    arry.CreateOneDim(VT_R8, 100);
    long i;
    for(i=0; i<100; i++)
    {
        double x, y;
        x = i/10.;
        y = sin(x);
        arrx.PutElement(&i, &x);
        arry.PutElement(&i, &y);
    }
    hr = spElem->Plot(COleVariant(arrx), COleVariant(arry));
    hr = m_spGraph->AutoRange();
    return TRUE;  // return TRUE  unless you set the focus to a control
}

Embeddable script editor for MFC applications


Objects Events Listing
Sample screenshot
Pop-Up Object properties and methods list
Sample screenshot
IntelliSense – like function parameters tooltips

Introduction

I’ve been searching the net for a suitable and affordable solution that will allow me to embed scripting functionality in my C++ application. What I found was either not sufficient or cost a lot of money.
So I’ve decided to make one of my own. After about a month of work and testing I’ve come up with this embeddable scripter.
I made it a separate DLL , which is really easy to use.

Usage

  1. Download the source code, and compile it. Make sure to compile the correct version (Debug\Release) or both.
  2. Include ScriptEditor.h in your project and add the library ScriptEditor.lib to your additional libraries. Make sure to reference the correct version! Debug version for debugging and release version for release!
  3. Copy the DLL to the same folder as your executable.
  4. In your code create an instance of CScripter and initialize it.
  5. Add your objects to the script and optionally the script text.

Example

In the header of your main application window add:

#include "..\ScriptEditor\Scripter.h"

Add member variable:

CScripter scripter;

Then when initializing add:

scripter.CreateEngine("VBScript");

After that add your objects to the script:

scripter.AddObject("MPlayer",(IDispatch *)m_mediaPlayer.GetControlUnknown());
scripter.AddObject("PlayButton",
      (IDispatch *)m_commandButton.GetControlUnknown());
scripter.AddObject("TabStrip",(IDispatch *)m_tabStrip.GetControlUnknown());
scripter.AddObject("SimpleObject",m_simpleObject.GetIDispatch(TRUE));

And optionally set the script text:

scripter.scriptText = "MsgBox \"Test message\"";

After that the script is ready to run, you now may do one of the following:

// Run the script
scripter.StartScript();
// Stop the script execution
scripter.StopScript();
// Open the script editor window
scripter.LaunchEditor();
// Reset the script (All added objects are removed and engine is recreated)
scripter.Reset();

Building a simple C++ script compiler from Scintilla and CINT

Personal compiler

Personal_C___Compiler_src

Introduction

I ran across two Open Source projects recently. CINT, a C/C++ script engine, and Scintilla, a multi-language editor with syntax highlighting among other features.
So the possibilities here are obvious. A simple application that would allow the rapid entry, execution, and debugging of C/C++ scripts. What a handy tool for a developer such as myself. I would imagine this would also be of interest to anyone learning the C/C++ language.

Application Goals

There is tremendous opportunity here for features. But I decided to keep the feature set as small as possible so as to focus on the integration of the CINT engine and the Scintilla editor.
The features provided will enable you to enter your C/C++ code into the editor, execute it, then the program will rub your nose in all the numerous syntax errors you have inflicted on the digital world.
This application also statically links both projects, and I will cover the details needed to make this happen. Often with small applications like this, you know, without full blown installers, it’s nicer to static link to avoid frustrating users with missing DLLs. My personal preference is to static link if possible unless I have an installer that is capable of verifying the integrity of the installation. Disk space is cheaper than time these days.

Our project

The project is an MFC dialog application created with VC6. I avoided using MFC specific classes for integrating CINT or Scintilla, so you should have no problem porting this code to a non-MFC project.

Linking to Scintilla

From the Scintilla websiteScintilla is an editor control providing support for syntax styling, error indicators, code completion, and call tips. The selection margin can contain markers like those used in debuggers to indicate breakpoints and the current line. Styling choices are more open than with many editors, allowing the use of proportional fonts, bold and italics, multiple foreground and background colors, and multiple fonts.
I downloaded and extracted Scintilla into the sub folder scintilla in our project folder. We find the VC project files in the sub folder ./scintilla/vcbuild. After adding the SciLexer.dsp project to our workspace, we find it builds without error. Great!
By default, Scintilla compiles to a DLL. We would like to static link, so we will add a linker response file to create a static library. I created two files, one for the release version, and another for the debug version.
Linker Response File (rsp_scintilla.txt) – Release version.

/nologo /subsystem:windows ./Release/*.obj /out:../bin/s_scintilla.lib

Linker Response File (rsp_scintillad.txt) – Debug version.

/nologo /subsystem:windows ./Debug/*.obj /out:../bin/sd_scintilla.lib

Now we add a Post-Build Step to each of the Release and Debug versions, calling the appropriate response file.
Post-Build Step – Release version.

link -lib @rsp_scintilla.txt

Post-Build Step – Debug version.

link -lib @rsp_scintillad.txt

Build Scintilla, and you should find that the files sd_scintilla.lib and s_scintilla.lib have been created in the scintilla/bin folders. These are the libs we will link to.
We need to add Scintilla headers to our project, so I prefer to do this in the Stdafx.h file, since the Scintilla library files will probably not change much. So, in Stdafx.h, let’s add the following includes…

// Include Scintilla parser
#include "scintilla/include/SciLexer.h"
#include "scintilla/include/Scintilla.h"

Last step here, we need to link to the Scintilla lib files. Open the Stdafx.cpp file and add the following….

#ifdef _DEBUG
#   pragma comment( lib, "scintilla/bin/sd_scintilla.lib" )
#else
#   pragma comment( lib, "scintilla/bin/s_scintilla.lib" )
#endif

This will link us to the debug or release version as needed. You could also add the lib files to the project settings, I prefer this method.
Since the Scintilla project was a DLL, and we’re now linking statically, we need to reproduce the startup and shutdown procedures it would have done in the DLL. You can usually find this by searching the project for the function DllMain(). Sure enough, we find DllMain() in Scintilla. It registers the editor window class among other things. We will add this code to our startup. In InitInstance(), add…

// Initialize the Scintilla
if ( !Scintilla_RegisterClasses( AfxGetApp()->m_hInstance ) )
{   AfxMessageBox( "Scintilla failed to initiailze" );
    return FALSE;
} // end if

Add the Scintilla shutdown code to ExitInstance()

// Release Scintilla
Scintilla_ReleaseResources();

Great! We are now linked to the Scintilla library. Let’s move on to CINT.

Linking to CINT

From the CINT websiteCINT is a C/C++ interpreter aimed at processing C/C++ scripts.
CINT is a part of ROOT. ROOT has a fascinating feature set, and it would be interesting to integrate as well. I would have done so except that ROOT is covered by the LGPL, and being a commercial developer myself, this license would never allow me to use the work in an actual project. Scintilla and CINT are covered by more commercial friendly licenses. I would definitely like to update this project in the future to integrate ROOT as well.
I downloaded and extracted CINT into the subfolder cint in our project folder. Unfortunately, the VC project files for CINT are actually wrappers around a MAK file. Because of this, I chose to just create another project and add the files that I needed. I created the project files in the subfolder ./cint/libcint. This project natively creates static libraries, so there is nothing else to do here but compile.
Upon compiling, I ran into a small snag. Two functions, G__SetGlobalcomp() and G__ForceBytecodecompilation(), are defined in both Method.cxx and v6_dmystrm.c, and both files are needed to compile CINT. The v6_dmystrm.c versions are the ones we want, so I surrounded the two functions in Method.cxx with #if 0. I also had to do the same in Apiifold.cxx. I’m sure this is probably an oversight since the project is primarily for UNIX. Hopefully, this will be resolved in a later version.
Despite this minor hiccup, things are now compiling nicely. Let’s add it to our project by added the following includes to the the Stdafx.h file…

// CINT
#include "cint/G__ci.h"
#include "cint/src/Global.h"

We need to link as well, so we add the following lines to the Stdafx.cpp file…

#ifdef _DEBUG
#   pragma comment( lib, "cint/libcint/Debug/libcint.lib" )
#else
#   pragma comment( lib, "cint/libcint/Release/libcint.lib" )
#endif

Creating the Scintilla Editor and Output windows

Now that both libraries are linked in, we can start the fun part of actually using them. To start, let’s create the Scintilla editor by calling the function InitialiseEditor() from OnInitDialog(). We require a list of C/C++ keywords and a color scheme to complete the initialization. I used a color scheme I like: the original Twilight scheme from the old Borland compiler for DOS from which I learned C++ many moons ago. It should be straightforward to modify the colors to your own taste.

// C++ keywords
static const char g_cppKeyWords[] =
    // Standard
    "asm auto bool break case catch char class const "
    "const_cast continue default delete do double "
    "dynamic_cast else enum explicit extern false finally "
    "float for friend goto if inline int long mutable "
    "namespace new operator private protected public "
    "register reinterpret_cast register return short signed "
    "sizeof static static_cast struct switch template "
    "this throw true try typedef typeid typename "
    "union unsigned using virtual void volatile "
    "wchar_t while "
    // Extended
    "__asm __asume __based __box __cdecl __declspec "
    "__delegate delegate depreciated dllexport dllimport "
    "event __event __except __fastcall __finally __forceinline "
    "__int8 __int16 __int32 __int64 __int128 __interface "
    "interface __leave naked noinline __noop noreturn "
    "nothrow novtable nullptr safecast __stdcall "
    "__try __except __finally __unaligned uuid __uuidof "
    "__virtual_inheritance";
/// Scintilla Colors structure
struct SScintillaColors
{   int         iItem;
    COLORREF    rgb;
};
// A few basic colors
const COLORREF black = RGB( 0, 0, 0 );
const COLORREF white = RGB( 255, 255, 255 );
const COLORREF green = RGB( 0, 255, 0 );
const COLORREF red = RGB( 255, 0, 0 );
const COLORREF blue = RGB( 0, 0, 255 );
const COLORREF yellow = RGB( 255, 255, 0 );
const COLORREF magenta = RGB( 255, 0, 255 );
const COLORREF cyan = RGB( 0, 255, 255 );
/// Default color scheme
static SScintillaColors g_rgbSyntaxCpp[] =
{
    {   SCE_C_COMMENT,          green },
    {   SCE_C_COMMENTLINE,      green },
    {   SCE_C_COMMENTDOC,       green },
    {   SCE_C_NUMBER,           magenta },
    {   SCE_C_STRING,           yellow },
    {   SCE_C_CHARACTER,        yellow },
    {   SCE_C_UUID,             cyan },
    {   SCE_C_OPERATOR,         red },
    {   SCE_C_PREPROCESSOR,     cyan },
    {   SCE_C_WORD,             cyan },
    {   -1,                     0 }
};
void CCintDlg::InitialiseEditor()
{
    // Punt if we already have a window
    if ( ::IsWindow( m_hwndEditor ) ) return;
    // Create editor window
    m_hwndEditor = CreateWindowEx(  0, "Scintilla", "",
                                        WS_CHILD | WS_VISIBLE | WS_TABSTOP |
                                        WS_CLIPCHILDREN,
                                        10, 10, 500, 400,
                                        GetSafeHwnd(), NULL /*(HMENU)GuiID*/,
                                        AfxGetApp()->m_hInstance, NULL );
    // Did we get the editor window?
    if ( !::IsWindow( m_hwndEditor ) )
    {   TRACE( "Unable to create editor window\n" );
        return;
    } // end if
    // CPP lexer
    SendEditor( SCI_SETLEXER, SCLEX_CPP );
    // Set number of style bits to use
    SendEditor( SCI_SETSTYLEBITS, 5 );
    // Set tab width
    SendEditor( SCI_SETTABWIDTH, 4 );
    // Use CPP keywords
    SendEditor( SCI_SETKEYWORDS, 0, (LPARAM)g_cppKeyWords );
    // Set up the global default style. These attributes
    // are used wherever no explicit choices are made.
    SetAStyle( STYLE_DEFAULT, white, black, 10, "Courier New" );
    // Set caret foreground color
    SendEditor( SCI_SETCARETFORE, RGB( 255, 255, 255 ) );
    // Set all styles
    SendEditor( SCI_STYLECLEARALL );
    // Set selection color
    SendEditor( SCI_SETSELBACK, TRUE, RGB( 0, 0, 255 ) );
    // Set syntax colors
    for ( long i = 0; g_rgbSyntaxCpp[ i ].iItem != -1; i++ )
        SetAStyle( g_rgbSyntaxCpp[ i ].iItem, g_rgbSyntaxCpp[ i ].rgb );
}

I also used Scintilla for the output window. Just to show a different method. I accessed the output window more directly using ::SendMessage().

void CCintDlg::InitialiseOutput()
{
    // Punt if we already have a window
    if ( ::IsWindow( m_hwndOutput ) ) return;
    // Create editor window
    m_hwndOutput = CreateWindowEx(  0, "Scintilla", "",
                                        WS_CHILD | WS_VISIBLE | WS_TABSTOP |
                                        WS_CLIPCHILDREN,
                                        10, 10, 500, 400,
                                        GetSafeHwnd(), NULL /*(HMENU)GuiID*/,
                                        AfxGetApp()->m_hInstance, NULL );
    // Did we get the editor window?
    if ( !::IsWindow( m_hwndEditor ) )
    {   TRACE( "Unable to create editor window\n" );
        return;
    } // end if
    // Set number of style bits to use
    ::SendMessage( m_hwndOutput, SCI_SETSTYLEBITS, 5, 0L );
    // Set tab width
    ::SendMessage( m_hwndOutput, SCI_SETTABWIDTH, 4, 0L );
    // Set foreground color
    ::SendMessage( m_hwndOutput, SCI_STYLESETFORE,
                   STYLE_DEFAULT, (LPARAM)RGB( 255, 255, 255 ) );
    // Set background color
    ::SendMessage( m_hwndOutput, SCI_STYLESETBACK, STYLE_DEFAULT, (LPARAM)RGB( 0, 0, 0 ) );
    // Set font
    ::SendMessage( m_hwndOutput, SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)"Courier New" );
    // Set selection color
    ::SendMessage( m_hwndOutput, SCI_SETSELBACK, (WPARAM)TRUE, (LPARAM)RGB( 0, 0, 255 ) );
    // Set all styles
    ::SendMessage( m_hwndOutput, SCI_STYLECLEARALL, 0, 0L );
}

To position the windows, I used the following code. It’s straightforward as we are just dealing with plain old window handles…

BOOL CCintDlg::Size()
{
    // Ensure valid window
    if ( !::IsWindow( GetSafeHwnd() ) )
        return FALSE;
    // Get window size
    RECT rect, ctrl;
    GetClientRect( &rect );
    CopyRect( &ctrl, &rect );
    // Position the editor window
    ctrl.bottom -= ( 6 * 24 );
    CWnd *pWnd = CWnd::FromHandle( m_hwndEditor );
    if ( pWnd ) pWnd->MoveWindow( &ctrl );
    // Position the output window
    ctrl.top = ctrl.bottom;
    ctrl.bottom = rect.bottom;
    pWnd = CWnd::FromHandle( m_hwndOutput );
    if ( pWnd ) pWnd->MoveWindow( &ctrl );
    return TRUE;
}

Executing the code with CINT

Of course, we want to be able to execute the script, and have any output generated accessible in a cut-and-pasteable window. CINT sends the output from the script to the standard output stream STDOUT. We need to intercept this data. So to make this simple, I appropriated the class CHookStdio from the Open Source project rulib (don’t worry, no license violation here). CHookStdio allows us to easily hook STDOUT and access the data. Note that we have to pass a parameter to CHookStdio indicating how much buffer space we need. Be aware of this size when writing to your scripts. I set this to 64K.
So the steps to execute our script are now…

  • Get the text from Scintilla
  • Hook STDOUT
  • Send to CINT for processing
  • Check for CINT errors
  • Write the hooked STDOUT data to the output window

And here are the details.

void CCintDlg::OnExecute()
{
    // Reset CINT
    G__scratch_all();
    g_sCintLastError = "";
    // Reset Scintilla
    SendEditor( SCI_MARKERDELETEALL, 0 );
    // Clear output window
    ::SendMessage( m_hwndOutput, SCI_SETTEXT, 0, (WPARAM)"" );
    // Get the editor window handle
    CWnd *pWnd = CWnd::FromHandle( m_hwndEditor );
    if ( !pWnd ) return;
    // Get the script text
    CString strScript;
    pWnd->GetWindowText( strScript );
    if ( strScript.IsEmpty() ) return;
    // Must add to get proper line number
    strScript = "#line 0\r\n" + strScript;
    // Hook stdio output
    CHookStdio hs( STD_OUTPUT_HANDLE );
    // Set error callback function
    G__set_errmsgcallback( &CCintDlg::CintError );
    // Execute the program
    if ( !G__int( G__exec_text( (LPCTSTR)strScript ) ) )
    {
        // Initilaize error markers
        SendEditor( SCI_MARKERDEFINE, 0, SC_MARK_SHORTARROW );
        SendEditor( SCI_MARKERSETFORE, 0, RGB( 80, 0, 0 ) );
        SendEditor( SCI_MARKERSETBACK, 0, RGB( 255, 0, 0 ) );
        // Set error marker to proper line
        int nErrLine = G__lasterror_linenum();
        SendEditor( SCI_MARKERADD, nErrLine - 1, 0 );
        // Show the error string
        ShowError( g_sCintLastError.c_str() );
        return;
    } // end if                
    // Set foreground color
    ::SendMessage( m_hwndOutput, SCI_STYLESETFORE,
                   STYLE_DEFAULT, (LPARAM)RGB( 255, 255, 255 ) );
    ::SendMessage( m_hwndOutput, SCI_STYLECLEARALL, 0, 0L );
    // Get output
    char buf[ 64 * 1024 ] = "";
    buf[ hs.Read( buf, sizeof( buf ) - 1 ) ] = 0;
    // Show script output
    if ( *buf ) ::SendMessage( m_hwndOutput, SCI_SETTEXT, 0, (WPARAM)buf );
}

This function also includes the code for highlighting errors in the script. Here is a screenshot of what an error looks like.
Personal compiler with error

Conclusion

That sums it up. So have fun.
One of the many practical uses I can think of for this project is generating tedious lookup tables which often come up when optimizing code. Now I can create a simple script and run it when I needed, instead of wasting time with an entire project just to generate a simple table. (Although I have to admit to using PHP for this lately.)
Here is a script to generate a square root lookup table…

// Start the table
printf( "const double g_rdLookup_Sqrt[] = \r\n{\r\n\t" );
// Generate items
for ( int i = 0; i < 100; i++ )
{
    // Add value separator
    if ( i ) printf( ", " );
    // Break into reasonable pieces
    if ( i && !( i % 8 ) ) printf( "\r\n\t" );
    // Calculate value
    printf( "%g", (double)sqrt( (double)i ) );
}
// Complete the table
printf( "\r\n};" );

And here’s the output…

const double g_rdLookup_Sqrt[] =
{
    0, 1, 1.41421, 1.73205, 2, 2.23607, 2.44949, 2.64575,
    2.82843, 3, 3.16228, 3.31662, 3.4641, 3.60555, 3.74166, 3.87298,
    4, 4.12311, 4.24264, 4.3589, 4.47214, 4.58258, 4.69042, 4.79583,
    4.89898, 5, 5.09902, 5.19615, 5.2915, 5.38516, 5.47723, 5.56776,
    5.65685, 5.74456, 5.83095, 5.91608, 6, 6.08276, 6.16441, 6.245,
    6.32456, 6.40312, 6.48074, 6.55744, 6.63325, 6.7082, 6.78233, 6.85565,
    6.9282, 7, 7.07107, 7.14143, 7.2111, 7.28011, 7.34847, 7.4162,
    7.48331, 7.54983, 7.61577, 7.68115, 7.74597, 7.81025, 7.87401, 7.93725,
    8, 8.06226, 8.12404, 8.18535, 8.24621, 8.30662, 8.3666, 8.42615,
    8.48528, 8.544, 8.60233, 8.66025, 8.7178, 8.77496, 8.83176, 8.88819,
    8.94427, 9, 9.05539, 9.11043, 9.16515, 9.21954, 9.27362, 9.32738,
    9.38083, 9.43398, 9.48683, 9.53939, 9.59166, 9.64365, 9.69536, 9.74679,
    9.79796, 9.84886, 9.89949, 9.94987
}

Adding VBScript and JScript support in your C++ applications

ScreenShots

ScriptDemo_demo

Introduction

I am always amazed to see how the script control (msscript.ocx) is fun to use and at the same time how C++developers reacted when it’s time to use. Maybe the extension (.ocx) make them feel, it’s visual basic! In this article, I would like to remove those frontiers and give some new reasons for C++ developers to use it.

Description

To use either VBScript or JScript is fairly simple in a VB and C++ Application thanks to Microsoft’s development efforts to create Windows Scripting technology. A developer only needs to know how to use the MicrosoftScripting ActiveX control (msscript.ocx) and how to pass value to a script method. For this reason, the first wrapper class that I want to identify is the CScriptObject. This wrapper is very simple to use and it provides most of the functionality that you will want to use in your application. It has a function to load script (text data) from a file or resource, get a list of methods name, selecting script language and to the execute function and statement. This class has no dependencies on MFC and can also be used in a console application.
First of all to call a script it is important to know that VBScript and JScript deal only with VARIANT parameters. This is the reason I created the CSafeArrayHelper class. The

CSafeArray

helper wrapper class allows you to create parameters that you will pass to your script function.

class CSafeArrayHelper
{
    public:
        CSafeArrayHelper();
        ~CSafeArrayHelper();
    bool Create(VARTYPE  vt, UINT  cDims, UINT lBound, UINT cCount);
    bool Destroy();
    UINT GetDimension();
    bool Attach(LPSAFEARRAY psa);
    bool AttachFromVariant(VARIANT* pVariant);
    LPSAFEARRAY Detach();
    LPSAFEARRAY GetArray();
    bool AccessData(void FAR* FAR* pvData);
    bool UnaccessData();
    bool Lock();
    bool Unlock();
    bool PutElement(long lIndices, void FAR* vData);
    bool GetElement(long lIndices, void FAR* vData);
    VARIANT GetAsVariant();
    protected:
    LPSAFEARRAY    m_pSA;
    private:
};

It provides the exact same features that you will want to use with SAFEARRAY object but its usage may be simpler for some of us (like me!). The function GetAsVariant may be useful in case when you want to view the type of data that was encapsulated in your SAFEARRAY. This function could not provide ways to read all data types since the SAFEARRAY Data type (fFeatures) didn’t implement it. Nonetheless to say, this function do a guess on the data types.

How to use

First to use this control, I will recommend you to take a look at the documentation for VBScript and JScript to know all you can do within your script function.

Writing a Script function

Let’s say we want to create a simple function to convert temperature from Fahrenheit to Celsius.
In VBScript write:

Function Celsius(fDegrees)
   Celsius = (fDegrees - 32) * 5 / 9
End Function

or in JScript write:

function Celsius(fDegres)
{
   return (fDegres-32)*5/9;
}

To call this function, one only needs to store each parameter into VARIANT. Since your function (method) can have more than one parameter, a SAFEARRAY is needed to encapsulated them. In that latter case, you may want to view the parameter count for the array passed to your function by checking the .length property for string function or by some other means.

function CountParam(aParam)
{
    var strPresent = "Parameter is : " + (aParam.length>0 ? "Present": "Not present");
    return strPresent;
}

The same technique may be used in VBScript. This allows you to detect variable length argument at run time. To call a function without argument, a SAFERRAY is created but without parameter.

Calling a Script function

Your code can be as easy as this:

void CScriptDemoDlg::OnBtnExecute()
{
    CString strParam, strProc;
    m_ctlParameter.GetWindowText( strParam );
    m_ctlFunctions.GetWindowText( strProc );
    CSafeArrayHelper sfHelper;
    try{
        _variant_t var;
        if (strProc.IsEmpty())
            sfHelper.Create(VT_VARIANT, 1, 0, 0);    // (void) parameter
        else
        {
            sfHelper.Create(VT_VARIANT, 1, 0, 1);    // 1 parameter
            var = _bstr_t(strParam);
        }
        sfHelper.PutElement(0, (void*)&var);    // parameter1 -> index 0
        LPSAFEARRAY sa =  sfHelper.GetArray();
        _variant_t varRet;
        if (m_ScriptObj.RunProcedure(strProc, &sa, &varRet))
            m_ctlResult.SetWindowText( (LPCTSTR)(_bstr_t(varRet)) );
        else
        {
            CString strError = m_ScriptObj.GetErrorString();
            m_ctlResult.SetWindowText( strError );
        }
    }
    catch(...)
    {
        CString strError = m_ScriptObj.GetErrorString();
        m_ctlResult.SetWindowText( strError );
    }
}

Some Ideas

Some of the ideas that you may want to try.

  1. You may want to have your script acts like a plugin, one suggestion is to have a resource script into a DLL and loads it at runtime (you may also have it part of your application). In that case, you will want to have specific module-related function, like: InitModule, ReleaseModule, btnOK_Click, btnCancel_Click,LoadUserData(strUsername), SaveUserData(strUserData), etc… and each of your DLL will have to implement them.
  2. You may have your script to do a complete task and you will load the script file based on the task (the CScriptObject class can load a script file for you!).Example: This script starts the “Calculator” program.
    function StartCalc()
    {
     var WshShell = new ActiveXObject("WScript.Shell");
     var oExec = WshShell.Exec("calc");
         WshShell = null;
    }
  3. You may want to create ActiveX object that lives longer than for a function call:
    var XML_Obj;
    function StartModule()
    {
     XML_Obj = new ActiveXObject("Msxml.DOMDocument");
     XML_Obj.async = false;
    }
    function StopModule()
    {
     XML_Obj = null;
    }
    function LoadSettings(strFilename)
    {
     XML_Obj.load(strFilename);
    }
  4. There are cases that you may want to execute the script code directly, just add the code, do not create a function…try it for fun!

References

Microsoft Windows Script Control
VBScript Documentation
JScript Documentation

C# Scripts using DynamicMethod

C#으로 Script를 구동하도록 만들어진 훌륭한 소스다
향후 유용하게 사용될 수 있겠다.

Introduction

CSSDemo
I had the problem to host active content in my document files, which included small scripts for animations and object specific interactions, like JavaScripts in HTML.
C# offers the great possibility to compile your own assemblies at runtime. However, there is no possibility to unload such compilations, to unload dynamic generated assemblies at runtime. The only way to do this is to create such assemblies in your own Domain and to unload such Domains later on but the communication between Domains is slow like inter process communication. Additional, to load the C# compiler environment and the compilation itself is not very fast at runtime, not nice for documents with hundreds of small internal scripts.
There are already quite a few articles about dynamic code generation using .NET and how to ship around these problems, but nothing was good enough for my case. The idea was to write my own C# script compiler based on C# syntax and conventions and to use Dynamic Methods to generate IL for best performance.
I found out that this works well without any assembly generation. With such solution, it is possible to use all existing classes and value structures but it is not possible to define your own new classes. The reason for this is that a .NET class always needs an assembly and the related assembly information.
However, the script itself works like a unique class with member functions and variables.

Using the Demo

For demonstration purposes, I wrote a small and very limited test program, only three C# files:
Program.cs contains a very simple user interface and EditCtrl.cs a simple code editor control.
The file Script.cs contains the class Script and this class is easy to use in other C# projects.
The demo looks like this and can be used to check and debug functionality and speed, the directory Demos contains some demo scripts for this.

Using the Code

To use the code in other C# projects, it is only necessary to import the class Script from Script.cs. After this is done, it’s possible to use the Script class like this:

var script = new Script();
script.Code = "using System.Windows.Forms; MessageBox.Show(\"Hello World!\");";
script.Run(null);

The second line in Script.cs contains the expression #define TraceOpCode. If this is defined (currently only in DEBUG), the Debug Output window will show the current MSIL output.
For this simple example, it is only:

ldstr Hello World!
call System.Windows.Forms.DialogResult Show(System.String)
pop
ret

How It Works

The namespace System.Reflection.Emit contains the class DynamicMethod. This class exists since .NET FrameWork version 2.0.
It is possible to use the DynamicMethod class to generate and execute methods at run time, without having to generate a dynamic assembly and a dynamic type to contain the method. Dynamic methods are the most efficient way to generate and execute small amounts of code. A good reference of how to use and an example code can be found here.
The Script class encapsulates a simple array of Dynamic methods: DynamicMethod[] methods. Every scriptfunction and the script body as creator is compiled to one of the dynamic methods in this array.
For this, the Script class contains the private helper class Script.Compiler to translate the script code to MSIL instructions using the ILGenerator from DynamicMethod.
After this own compilation, the .NET Framework just-in-time (JIT) compiler can translate the MSIL instructions to native machine code.
In difference to script interpreters, we get fast machine code for each supported CPU architecture.

ContainerListView and TreeListView: Writing VS.NET design-surface compatible controls

참조https://www.codeproject.com/Articles/3273/ContainerListView-and-TreeListView-Writing-VS-NET

ExtendedListViews_exe
ExtendedListViews_lib
extendedlistviews_src
TreeListView - extendedlistviews.gif
ContainerListView - extendedlistviews2.gif

Introduction

In today’s world, I’ve found that it becomes increasingly more complicated to render data in a meaningful and compact way. I’ve also found that the variety of controls, particularly ones for sale, seem to be created in a hurry, either because of time constraints or simply because of the desire to make money as cheaply as possible.
It was because of a lack of quality in existing components that I decided to take the time and create two controls that I desperately needed. The first is a listview that provides containers for controls or an image for every column, rather than only allowing text. The second is a quality hybrid tree-list that also provides the same features of the above mentioned list.
This article will overview these two controls, ContainerListView and TreeListView. Both controls were written purely using .NET classes in C#. I tried to avoid external API calls as much as possible. These two controls both make use of library for .NET, created by Pierre Arnaud, OPaC Bright Ideas. Pierre’s library allows the use of WindowsXP visual styles, by providing a wrapper for uxtheme.dll functions. This excellent library is available for download here on CodeProject, and an extended version (adds DrawThemeEdge function, required for these controls) is available in the zip. Many thanks to Pierre for creating this library, its served me well many times.
This is my first attempt at writing custom controls. Some of the logic is not optimized and is a little bloated. Several functions suck up a lot of CPU cycles, mostly the OnMouseDown code which tries to match a mouse click position to a visible item. I’m open to suggestions and bug fixes, and would greatly appreciate them. I will continue to work on both controls, adding features and optimizations however and wherever possible.

Topic focus

This article does not focus much on the creation of these two controls, although it does touch on some points. The inspiration for this article, in truth, was the amount of time I spent, trying to learn how to successfully integrate a control with the Visual Studio.NET design environment. Which was too much time. Both controls are fully compatible with the design surface in VS.NET, utilizing UITypeEditors and properly serializing source code.
The steps to accomplish such a task are very simple, but poorly documented and not well known. Most control developers skip the process of integrating their control with the design surface. My goal with this article is to familiarize you with .NET’s design features, and the few simple steps to add proper code serialization to your control.

The ContainerListView control

The standard .NET ListView control supports multiple columns, which can each hold a unique text value. In many instances, that simplicity is enough, or all that’s needed. All too often, though, the need arises to embed something other than text in a ListView‘s column.
The ContainerListView provides the ability to embed text, an image, or a control into each subitem of a ListView item. The control also adds a few fancy features, like column and row tracking, 3 unique context menus (column header, row, general), WindowsXP visual styles integration, and the ability to edit the items in design mode (including subitems and their image or control). The control also wires MouseDown events fired from a subitems control to the ContainerListView itself, ensuring proper row selection even when clicking on a sub-control.
Creation and population of a ContainerListView control is relatively simple. As mentioned, its entirely possible to drag and drop controls onto the design-surface in VS.NET, and have complete control over almost all design settings. If you prefer to manually code your GUI, here is an example:

ContainerListView clv = new ContainerListView();
clv.Text = "Sample ContainerListView";
clv.Name = "clv";
clv.Dock = DockStyle.Fill;
clv.VisualStyles = true;   // enable integration with
                           // WindowsXP visual styles
ContainerListViewItem clvi = new ContainerListViewItem();
clvi.Text = "Test";
// Add column headers
ToggleColumnHeader tch = new ToggleColumnHeader();
tch.Text = "Column 1";
clvi.Columns.Add(tch);
tch = new ToggleColumnHeader();
tch.Text = "Column 2";
clvi.Columns.Add(tch);
tch = new ToggleColumnHeader();
tch.Text = "Column 3";
clvi.Columns.Add(tch);
// Add a row item with a child progressbar
ContainerSubListViewItem cslvi = new ContainerSubListViewItem("Test");
clvi.SubItems.Add(cslvi);
ProgressBar pb = new ProgressBar();
pb.Value = 25;
cslvi = new ContainerSubListViewItem(pb);
clvi.SubItems.Add(cslvi);
clv.Items.Add(clvi);

This ContainerListView control inherits from the System.Windows.Forms.Control class, rather than extending the previously existing ListView control. Part of the goal was to see if I could create a control from scratch, and part was to keep it as low-profile as possible, without hooking into the Windows Common Controls (as the standard ListView does) or using Windows API calls. I wanted a purely .NET implementation. If you wish to examine the specifics of the control, take a look at the source code in the zip.

The TreeListView control

The TreeListView is a hybrid control. It blends a TreeView with the ContainerListView control above, allowing the first column to behave as a tree. TreeListView controls have become popular recently, and there are many available here on CodeProject and other sites. None had quite the features I was looking for, so I extended ContainerListView and added the tree. This TreeListView supports all the features of the ContainerListView except row tracking, which is currently in progress.
Here is an example:

TreeListView tlv = new TreeListView();
tlv.SmallImageList = smallImageList;
tlv.VisualStyles = true;
// Add column headers
ToggleColumnHeader tch = new ToggleColumnHeader();
tch.Text = "Tree Column";
tlv.Columns.Add(tch);
tch = new ToggleColumnHeader();
tch.Text = "Column 2";
tlv.Columns.Add(tch);
tch = new ToggleColumnHeader();
tch.Text = "Column 3";
tlv.Columns.Add(tch);
// Add tree nodes
TreeListNode tln = new TreeListNode();
tln.Text = "Test";
tln.ImageIndex = 1;
tln.SubItems.Add("Sub Item 1");
tln.SubItems.Add("Sub Item 2");
TreeListNode tln2 = new TreeListNode();
tln2.Text = "Child 1";
tln2.ImageIndex = -1; // Setting to -1 will suppress icon
tln2.SubItems.Add("Sub Item 1.1");
tln2.SubItems.Add("Sub Item 2.1");
tln2.Nodes.Add(tln2);
tlv.Nodes.Add(tln);
tln = new TreeListNode();
tln.Text = "Second Test";
tln.ImageIndex = 1;
tln.SubItems.Add("Test Item");
tln.SubItems.Add("Test Item");
tlv.Nodes.Add(tln);

Custom control rendering

The initial versions of these two controls used several private member functions to draw elements such as buttons, focus boxes, etc. A very large amount of code was required to render each state of the column header buttons, borders, etc.
The .NET framework provides a very handy class, the ControlPaint class in the System.Windows.Formsnamespace. This handy little class is loaded with static functions that provide rendering facilities for all sorts of objects, including buttons, borders, focus boxes, and plenty more. The use of the ControlPaint class can greatly reduce the amount of drawing code in your control, and help keep the proper Windows look and feel.
An important feature of these two controls is their Windows XP Visual Styles integration. This was accomplished using a library written by Pierre Arnaud. It wraps the visual styles rendering functions found in uxtheme.dll, and provides a very simple way to utilize them in .NET applications. This excellent library is available here on CodeProject: http://www.codeproject.com/cs/miscctrl/themedtabpage.asp. I highly recommend this library to anyone who wishes to easily integrate their controls with Windows XP. Pierre’s article also covers how to get tab pages to properly render under XP. Many thanks, Pierre, for a great library.

Integrating with the design surface

Now on to the meat of the article, integrating a control with the VS.NET design surface. Both these controls took approximately 4 days total, to write. Of those 4 days, over 3 were spent researching how to use .NET’s design facilities to make these controls as professional as possible, and as useful as possible.
Integrating a control, particularly a control that uses collections, requires the use of several design services available in .NET: UITypeEditors, TypeConverters, and design-time attributes. UITypeEditors provide a means of displaying a GUI editing dialog for any kind of object. TypeConverters provide a means of converting your custom classes into the proper source code. Design-time attributes provide the means to enable these design-time editing facilities in your control.

Selecting a UITypeEditor

Depending on your project, implementing a UITypeEditor can be very simple, or very complex. Using one of the supplied editors in the framework can make life very simple, and in very many cases, one of the supplied editors gets the job done very well. Other times, you may be required to write your own UITypeEditor, and that is beyond the scope of this article.
Some of the supplied UITypeEditors in the .NET framework follow. The System.Drawing.Design.FontEditor displays the standard font selection dialog when applied to a property. The default Control.Font property uses this editor. The System.Drawing.Design.ImageEditor displays an open file dialog filtered to image types, and is used in many places throughout the .NET framework. The System.Windows.Forms.Design.FileNameEditor displays the standard file open dialog for properties that require a filename. The System.Windows.Forms.Design.AnchorEditor displays the unique dropdown used for setting the Control.Anchor setting of every Windows Forms control.
The most unique and probably the most useful editor is the System.ComponentModel.Design.CollectionEditor. This displays a two-paned dialog box for editing collections of nearly any kind. The CollectionEditor expects only an indexer and add function in your collection to work properly with it. Implementing a CollectionEditor properly, ensuring proper code serialization, while not complex, is not documented well, and tedious to figure out by trial-and-error.

Integrating CollectionEditor into a control

To integrate a CollectionEditor for your control, you will need to do several, if not all, of the following tasks. Depending on how you implement the item class that will be contained in your collection class, there may be fewer things to implement than will be described here.
The first step in integrating CollectionEditor is developing your collection class and item class to be contained in that collection. A simple example follows:

public class MyCollectionItem
{
    // required for type converter
    public MyCollectionItem() { }
    #region Properties
    // properties here
    #endregion

    #region Methods
    // public methods here
    #endregion
}
public class MyCollection: CollectionBase
{
    // a basic indexer of the type of your collection 
    // item is required
    public MyCollectionItem this[int index]
    {
        get { return List[index]  as MyCollectionItem; }
        set { List[index] = value; }
    }
    // an Add method with a parameter of the type of
    // your collection item is required
    public int Add(MyCollectionItem item)
    {
        return List.Add(item);
    }
}

For the CollectionEditor to work, you must supply an indexer that takes an integer parameter, and returns the type of your collection item (in this case, MyCollectionItem), and an Add method that takes one parameter of the same type.
The next step requires that you add a property with a couple of attributes to your control class.

public class MyControl: Control
{
      protected MyCollection theCollection;
      [
        Category("Data"),
        Description("The collection of items for this control."),
        DesignerSerializationVisibility
        (DesignerSerializationVisibility.Content),
        Editor(typeof(CollectionEditor), typeof(UITypeEditor))
      ]
      public MyCollection TheCollection
      {
          get { return theCollection; }  // only getter, no setter
       }
}

The first two attributes are optional, specifying only where in the properties editor the property should go, and what it does. The next two are of more importance. The DesignerSerializationVisibility attribute instructs the design editor to serialize the contents of the collection to source code. This will place all the code required to add the items to a collection variable of your collection item type (in this case, MyCollectionItem).
The last attribute, Editor(), specifies what kind of editor the design editor should display. The attribute takes two arguments, System.Type, which specify the type of editor, and its parent type. In the example, we specified CollectionEditor, and UITypeEditor, from which all type editors should extend.
Integrating a CollectionEditor can become a little more complex at this point, although by no means hard. With the above examples, you will notice that no code is added to your source. This is because our collection item class, MyCollectionItem, inherits from nothing. The CollectionEditor only knows internally, how to serialize classes that extend Component. Often, simply extending Component will make CollectionEditorproperly serialize your code.
Sometimes, though, you may be required to implement a TypeConverter for your class. While it may seem intimidating to some, implementing a TypeConverter is, for the most part, a no brainer. This simple class will implement a TypeConverter for our MyCollectionItem class:

public class MyCollectionItemConverter: TypeConverter
{
    public override bool CanConvertTo
        (ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(InstanceDescriptor))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
     }
     public override object ConvertTo(ITypeDescriptorContext context,
             CultureInfo culture, object value, Type destinationType)
     {
         if (destinationType == typeof(InstanceDescriptor)
                     && value is MyCollectionItem)
         {
              MyCollectionItem item = (MyCollectionItem)value;
               ConstructorInfo ci = typeof(MyCollectionItem).
                                   GetConstructor(new Type[] {});
               if (ci != null)
               {
                     return new InstanceDescriptor(ci, null, false);
               }
          }
          return base.ConvertTo(context, culture, value, destinationType);
     }
}

All type converters must extend the TypeConverter class, and all must override CanConvertTo and ConvertTo methods. You should also call the base methods to make sure the converter can convert to types other than your collection item.
The second function does most of the work. It returns an InstanceDescriptor, which is a class in .NET that provides all the information required to create an instance of an object. In our type converter, we supply information about the constructor of our item class, and specify that the constructor does not describe the whole object. Specifying that this InstanceDescriptor only describes the constructor, will ensure that code to set the properties for your item class will be serialized to source.
Why do we need supply a TypeConverter? The CollectionEditor will attempt to serialize as much information about your class as it can in the form of properties. If your collection is part of a control, the CollectionEditor will need to know what the contstructor for your collection item is. Once it has this information, the CollectionEditor can add the MyControl.TheCollection.Add() lines to your source, adding each item to the collection contained in the control. Without knowledge of the constructor, the CollectionEditor can only create an instance of the collection item and add code to set its properties.
The final step in adding a CollectionEditor is adding two attributes to your collection item class. The first of these attributes will prevent instances of your class from cluttering the design surface. The second will associate your new TypeConverter with your item class.

[DesignTimeVisible(false), TypeConverter("MyCollectionItemConverter")]
public class MyCollectionItem
{
    // required for type converter
    public MyCollectionItem() { }
    #region Properties
    // properties here
    #endregion

    #region Methods
    // public methods here
    #endregion
}

Once you have your TypeConverter applied to your collection item class, you should be ready to go. A simple test with the design editor will let you know if the code is being serialized properly. In most cases, this should be enough. In extreme cases, you will need to implement ISerializable, and write your own serialization code for your class. That is beyond the scope of this article.

Final words

While there will be occasions that the information provided above will not help you successfully integrate a CollectionEditor into your control, my hope is that it will help most. Visual Studio .NET is a very rich development environment, and provides very powerful ways to implement design-surface compatible controls of your own. Many hard-core coders, including myself, prefer to code their UI manually. On the other hand, having a design editor available can save you in a pinch, and its important to have controls that work properly with it. I hope this article is useful to you, and I hope it will encourage the development of more design-aware controls.
The two controls introduced at the beginning of this article both implement CollectionEditors. This allows you to edit items and nodes in the design editor, if needed. CollectionEditors are even used nested, so when editing an item, you can open another editor to edit subitems. When adding controls to a ListView subitem, you must first add the control to the design surface. You will then be able to select it from a list in the subitems control property. Once the control is added to the list, you can still edit its properties simply by clicking on it. An unexpected side effect, but it was very welcome. I recommend caution when adding controls to ListViewsubitems. Not all controls will render properly, and some may have inadvertent issues when added. Controls tested so far have been ProgressBar, TextBox, PictureBox, and ComboBox.
Finally, I would like to ask two things of you if you have read this article and used the controls. First, please rate this article using the bar just above the comments. I’d like to get this moved from the “Unedited” section to a more appropriate section now that its more stable, and apparently that doesn’t happen unless people rate it.
Second, if you use these controls in a commercial application, some monetary support would be extremely helpful. I don’t have any plans to sell these controls right now, but the tech industry is a hard one to land a job in, and money doesn’t come easy for me. I don’t require that you pay for the controls, but if you can, you will really help me out. You can reach me through E-mail at jrista@hotmail.com if you would like to help.
Thanks for reading, and I hope the controls are useful to you. 🙂

Building Trees from Lists in .NET

Building Trees from Lists in .NET

, 2 Mar 2008 CPOL

An interface to simplify creating trees from lists of database rows or objects

 

Is your email address OK? You are signed up for our newsletters but your email address is either unconfirmed, or has not been reconfirmed in a long time. Please click here to have a confirmation email sent so we can confirm your email address and start sending you newsletters again. Alternatively, you can update Treelistyour subscriptions.

Who Is This Article For?

This article is aimed at .NET developers who need to build a tree (or forest) of objects by loading an array or list of rows from a database, and converting them to a tree structure in memory to use or display.

Introduction

Storing hierarchical data in a database is a very common requirement, whether it be product categories, sports tournaments, or staff hierarchies. Over the years, I have had to create, store in a database and display trees many times, and my method has evolved from having fixed-size hierarchies, to abstract classes providing tree functionality, and finally to the method described in this article. This article describes a very simple way to load data from a table, and convert it into a tree in C# code for .NET 2.0+.
While other articles have been written on the subject, the following goals are not always met:

  • You should not need to extend an abstract class, because your data class may already extend another class
  • You shouldn’t need to convert your class to be a partial class
  • You shouldn’t need to change your database table: as long as it uses the standard parent ID column referring to its own primary key, it should work
  • There shouldn’t need to be any type-casting of parent or child objects
  • It should be easy to use

The answer is to define an interface – in this case called ITreeNode<t /> – and then have a utility method to build a tree from that. This article assumes that you have a class defined to hold a single node (e.g. a Categoryobject), and that when you get a list of nodes from the database each reference to a parent has been instantiated as an object with its ID set, but without a reference to the fully populated parent object, nor its children.

An Example

Let’s look at the very common “Category” entity, used to categorise products. The database table looks like the following:
Table definition of a CategoryLet’s assume that there are just 8 rows in the database, which break the products into two main categories – “Hardware” and “Software” – and then further sub-categories. In this case “Operating Systems” and “Developer Tools” come under “Software”; “Monitors” and “Peripherals” under “Hardware”, and finally “Keyboards” and “Mice” under “Peripherals”, giving the following category hierarchy:
Example category hierarchyIn the database, the rows are as follows:
Category table rowsIn order to display this information on a website, you create a “Category” class:

public class Category
{
    private int _id;
    private string _name;
    private Category _parent;
    private List<Category> _children;
    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    public Category Parent
    {
        get { return _parent; }
        set { _parent = value; }
    }
    public List<Category> Children
    {
        get { return _children; }
        set { _children = value; }
    }
}

A method is also needed to retrieve all the categories from the database. This will be a “flat” list of each category, i.e. the _parent field will point to an object which only has its ID populated, and _children will be null. A possible example of this method is shown here:

static List<Category> GetListFromDatabase(DbConnection con) {
    DbCommand cmd = con.CreateCommand();
    cmd.CommandText = "SELECT Id, Name, ParentID FROM Category";
    cmd.CommandType = CommandType.Text;
    DbDataReader reader = cmd.ExecuteReader();
    List<Category> categories = new List<Category>();
    foreach (DbDataRecord row in reader) {
        Category c = new Category();
        c.Id = (int)row["Id"];
        c.Name = (string)row["Name"];
        if (row["ParentID"] != DBNull.Value)
        {
            c.Parent = new Category();
            c.Parent.Id = (int)row["ParentID"];
        }
        categories.Add(c);
    }
    reader.Close();
    return categories;
}

Once having a list of objects in memory, the ITreeNode interface comes in handy. The first step is to implement this interface:

public class Category : ITreeNode<Category> {
// contents of class remain as above, because the 
// interface is implemented by the Id, Parent, and 
// Children properties
}

The interface requires that we have one property referring to the parent of the category (where null represents a root-level node), and an IList pointing to the children.
Now we can call the TreeHelper utility methods to convert the flat array returned from GetListFromDatabase into a fully populated hierarchy:

IList<Category> topLevelCategories =
    TreeHelper.ConvertToForest(GetListFromDatabase());

The variable topLevelCategories contains two Categories: “Software” and “Hardware”.

Printing Out All Nodes using Nested HTML <ul> and <li> Tags

Using a recursive method, you can easily print out, for example, the full category hierarchy in nested <ul> tags, as follows:

void Page_Load(object sender, EventArgs e) {
    IList<Category> topLevelCategories =
        TreeHelper.ConvertToForest(Category.GetListFromDatabase());
    Response.Write("<ul>");
    foreach(Category topLevelCategory in topLevelCategories) {
        RenderCategory(topLevelCategory);
    }
    Response.Write("</ul>");
}
void RenderCategory(Category category) {
    Response.Write("<li>" + category.Name);
    if (category.Children.Count > 0) {
        Response.Write("<ul>");
        foreach(Category child in category.Children) {
            RenderCategory(child);
        }
        Response.Write("</ul>");
    }
    Response.Write("</li>");
}

This will render the following output:

  • Software
    • Operating Systems
    • Developer Tools
  • Hardware
    • Monitors
    • Peripherals
      • Keyboards
      • Mice

Searching For a Single Category in the Tree

// in a website, this may use the ASP.NET Cache object.
List<Category> categories = GetCategories();
int categoryId = int.Parse(Request.Params["categoryId"]);
Category currentCategory =
    TreeHelper.FindTreeNode(categories, categoryId);

Printing Breadcrumbs

Continuing the example above, this is how the bread crumbs for the current category could be printed:

Category currentCategory = GetCategory();
foreach(Category category in
    TreeHelper.Iterators.FromRootToNode(currentCategory))
{
    Response.Write(" / " + category.Name);
}

If the current category was “Keyboards”, this would render the following HTML:

/ Hardware / Peripherals / Keyboards

Tree Helper

The TreeHelper utility class contains numerous other useful methods – such as GetDepth and HasHierarchyLoop – and iterators – such as DepthFirstTraversal, BreadthFirstTraversal, ClimbToRoot, FromRootToNode, and Siblings.
Check out the fully-documented source code for the full details.

Using Extension Methods and “LINQ to Trees”

If you are using a .NET 3.5 solution, you are able to take advantage of extension methods. This has the effect of implementing methods in interface declarations (which is not possible in older versions of C#), which is probably the most useful aspect of extension methods, and indeed was the reason they were invented.
An example using extension methods:

List<Category> categories = GetCategories().ConvertToForest();
Category current = categories.FindCategory(3);
foreach(Category descendent in current.DepthFirstTraversal()) {
    Response.Write("Depth of " + descendent.Name + ": " + descendent.GetDepth();
}

Remember, ConvertToForest, FindCategory, DepthFirstTraversal and GetDepth are not implemented by the Category class, it simply “inherits” these methods from the TreeHelper class, simply by implementing ITreeNode<T>.
Extension methods go hand-in-hand with LINQ. Yes, strictly speaking, this is simply “LINQ to Objects” rather than “LINQ to trees”, but regardless, it is a new way to query your trees:

List<Category> categoryList = Category.GetCategories();
// Get all categories which are not top level categories, 
// and retrieve only the name.
var nonRootCategories =
    from c in categoryList.DepthFirstTraversalOfList()
    where c.Parent != null
    select new { Name = c.Name };
// Get all categories at Depth 2, ordered by name, and
// get the whole category object.
var level2Categories =
    from c in categoryList.DepthFirstTraversalOfList()
    where c.GetDepth() == 2
    orderby c.Name ascending
    select c;