Click here to Skip to main content
15,885,435 members
Articles / Desktop Programming / WPF
Article

OpenGL for Both Native and .NET Environment

Rate me:
Please Sign up or sign in to vote.
4.92/5 (12 votes)
7 Jan 2013CPOL3 min read 36.2K   2.3K   50   6
This article shows how you can bring OpenGL to both native and .NET environment.

Image 1

Introduction

This article shows how you can bring OpenGL to both native and .NET environment.

Background

A few days ago, I wrote a visualization and charting library (called OpenGraph library) for both native and .NET environment based on my existing C++ OpenGL code base. I decided to reveal the essential code to the public in the hope that people can benefit from my work.

Using the Code

The demo code consists of four simple projects:

  1. OpenGraphLib: A C++ OpenGL Windows DLL
  2. OpenGraphWin32Client: A C++ Win32 OpenGL application
  3. OpenGraphLib_Cli: A C++/CLI class library that wraps the C++ OpenGL Windows DLL
  4. OpenGraphWinformDialogClient2010: A C# WinForms OpenGL application

Each project has Debug, Release, Unicode Debug, and Unicode Release configurations. Because of the size of each project and the time constraint, I cannot go into too much details describing the code. The following is a very brief description of each project.

Part One – OpenGraphLib

The part is a pure C++ Windows DLL (Unicode compliant). The main interface class is COpenGraph, which provides interfaces to setup and use OpenGL functions. The interface is clean because the actual implementation is done in the CDrawManager class. Clients (callers) of this code are agnostic about OpenGL. For example, the native Win32 or MFC clients do not need to include OpenGL library headers.

C++
//
// forward declaration
struct CGLFont2d;
class CRubberBandTracker;
class CDrawManager;

class OPENGRAPHLIB_API COpenGraph
{
public:
    COpenGraph(void);
    virtual ~COpenGraph(void);

public:
    enum {modeNONE = -1,modeTRACK, modeROTATE };

private:
    enum {GRID_LIST = 0, NODE_LIST, LINE_LIST, TRIANGLE_LIST, QUAD_LIST, TEXT_LIST,END_LIST};

public:
    bool Initialize(HWND hWnd);
    void SetMode(int mode);
    void SetWindowSize(int cx, int cy);
    void Render();
    void ForceRender();
    void ShowAxes(bool bShow);
    BOOL OnLButtonDown(RECT& rect, UINT nFlags, POINT point);
    void OnMouseMove(UINT nFlags, POINT point);
    HCURSOR GetModeCursor();
    void OnSetCursor();
    void Rotate(double fRx, double fRy, double fRz);
    bool TrackRect(HWND hWnd, POINT point);
    RECT GetTrackRect()const; // un-normalized
    void Clear();
    void SelectAll();
    void UnSelectAll();
    void ReverseSelectAll();
    void FreeMemory(void * p);
    void FreeMemoryArray(void * p);
    CIdentity AddNode(int id,CPointf pt, int nSize, COLORREF color,COLORREF colorSelected, 
                      int nStatus = STATUS_UNSELECTED, LPCTSTR tag = 0, LPCTSTR userData = 0); 
    CIdentity AddLine(int id,CPointf pt1,CPointf pt2, int nThickness, COLORREF color,
                      COLORREF colorSelected, int nStatus = STATUS_UNSELECTED, 
                      LPCTSTR tag = 0, LPCTSTR userData = 0 );

private:
    CDrawManager* m_pDrawManager;
};

Part Two – OpenGraphWin32Client

This is a Win32 Windows application that uses the Windows native DLL (OpenGraph.dll). The COpenGraph object is associated with the main window at InitInstance(). Windows messages are handled appropriately in WndProc() for OpenGL window size setup, mouse events, and rendering. The WM_ERASEBKGND message is ignored so we do not have flickering when the window is being resized. The actual drawing objects are added by some helper functions. They are pretty simple and are not shown here.

C++
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, 
                       CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   m_openGraph.Initialize(hWnd);

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		m_openGraph.Render();
		EndPaint(hWnd, &ps);
		break;

    // jxu
    case WM_ERASEBKGND:
        break;

    case WM_SIZE:
        {
            m_nCx    = LOWORD(lParam );
            m_nCy = HIWORD(lParam );
            m_openGraph.SetWindowSize(m_nCx, m_nCy);
        }
        break;

    case WM_LBUTTONDOWN:
        {
            RECT rect;
            UINT nFlags = GET_KEYSTATE_WPARAM(wParam);
            POINT pt;
            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam); 
            m_openGraph.OnLButtonDown(rect, nFlags, pt);
        }
        break;

    case WM_MOUSEMOVE:
        {
            m_openGraph.OnSetCursor();  // jxu-  Win32 app is different from MFC
            UINT nFlags = GET_KEYSTATE_WPARAM(wParam);
            POINT pt;
            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam); 
            m_openGraph.OnMouseMove(nFlags, pt);
        }
        break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

Part Three – OpenGraphLib_Cli

The part is a C++/CLI .NET class library (OpenGraph_Cli.dll). It serves as a bridge between the native C++ DLL and the .NET client. For more details about C++/CLI, please refer to my related CodeProject article entitled “Leveraging your Existing C++ Code for Use in the .NET Environment”.

Part Four – OpenGraphWinformDialogClient2010

This is WinForms application that displays OpenGL on a user control. It only interfaces with the C++/CLI .NET class library (OpenGraph_Cli.dll). Please note that the platform target must be x86.

The user control OpenGLUserControl is empty and only contains some delegates that will be set by the parent WinForm.

C#
public partial class OpenGLUserControl : UserControl
{
    public delegate void OpenGLUserControl_Load_Type(object sender, EventArgs e);
    public delegate void OpenGLUserControl_Resize_Type(object sender, EventArgs e);
    public delegate void OpenGLUserControl_MouseDown_Type(object sender, MouseEventArgs e);
    public delegate void OpenGLUserControl_MouseMove_Type(object sender, MouseEventArgs e);
    public delegate void OpenGLUserControl_Paint_Type(object sender, PaintEventArgs e);

    public OpenGLUserControl_Load_Type Load_CallBack = null;
    public OpenGLUserControl_Resize_Type Resize_CallBack = null;
    public OpenGLUserControl_MouseDown_Type MouseDown_CallBack = null;
    public OpenGLUserControl_MouseMove_Type MouseMove_CallBack = null;
    public OpenGLUserControl_Paint_Type Paint_CallBack = null;

    public OpenGLUserControl()
    {
        InitializeComponent();
    }

    private void OpenGLUserControl_Load(object sender, EventArgs e)
    {
        if (Load_CallBack != null)
        {
            Load_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_Resize(object sender, EventArgs e)
    {
        if (Resize_CallBack != null)
        {
            Resize_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_MouseDown(object sender, MouseEventArgs e)
    {
        if (MouseDown_CallBack != null)
        {
            MouseDown_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_MouseMove(object sender, MouseEventArgs e)
    {
        if (MouseMove_CallBack != null)
        {
            MouseMove_CallBack(sender, e);
        }
    }

    private void OpenGLUserControl_Paint(object sender, PaintEventArgs e)
    {
        if (Paint_CallBack != null)
        {
            Paint_CallBack(sender, e);
        }
    }
}

The WinForm sets the delegates for the user control, sets the appropriate drawing mode, and adds drawing objects.

C#
public partial class Form1 : Form
{
    COpenGraph_Cli _openGraph_Cli = new COpenGraph_Cli();
    private int m_nCx = 0;
    private int m_nCy = 0;
    private bool m_bShowAxes = true;

    public Form1()
    {
        InitializeComponent();

        openGLUserControl.Load_CallBack = this.Delegate_Load;
        openGLUserControl.MouseMove_CallBack = this.Delegate_MouseMove;
        openGLUserControl.MouseDown_CallBack = this.Delegate_MouseDown;
        openGLUserControl.Paint_CallBack = this.Delegate_Paint;
        openGLUserControl.Resize_CallBack = this.Delegate_Resize;
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void Form1_Resize(object sender, EventArgs e)
    {
        m_nCx = this.openGLUserControl.Width;
        m_nCy = this.openGLUserControl.Height;
        _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        _openGraph_Cli.Render();
    }


    private void Delegate_Load(object sender, EventArgs e)
    {
        _openGraph_Cli.Initialize(openGLUserControl.Handle);
        m_nCx = this.openGLUserControl.Width;
        m_nCy = this.openGLUserControl.Height;
        _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        _openGraph_Cli.Render();
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeNONE);

    }

    private void Delegate_MouseMove(object sender, MouseEventArgs e)
    {
        _openGraph_Cli.OnSetCursor();
        if (e.Button == MouseButtons.Left)
        {
            CRect_Cli rect = new CRect_Cli();
            CPoint_Cli pt = new CPoint_Cli();
            pt.X = e.X;
            pt.Y = e.Y;

            uint nFlags = (int)COpenGraph_Cli.ButtonFlag.flagMK_LBUTTON;
            if (_openGraph_Cli != null)
            {
                _openGraph_Cli.OnMouseMove(nFlags, pt);
            }
        }
    }

    private void Delegate_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            CRect_Cli rect = new CRect_Cli();
            CPoint_Cli pt = new CPoint_Cli();
            pt.X = e.X;
            pt.Y = e.Y;
            uint nFlags = 0;
            if (_openGraph_Cli != null)
            {
                _openGraph_Cli.OnLButtonDown(ref rect, nFlags, pt);
            }
        }
    }

    private void Delegate_Paint(object sender, PaintEventArgs e)
    {
        if (_openGraph_Cli != null)
        {
            _openGraph_Cli.Render();
        }
    }

    private void Delegate_Resize(object sender, EventArgs e)
    {
        if (_openGraph_Cli != null)
        {
            m_nCx = this.ClientSize.Width;
            m_nCy = this.ClientSize.Height - menuStrip1.Height;
            _openGraph_Cli.SetWindowSize(m_nCx, m_nCy);
        }
    }


    // draw -------------------------------------------------------
    private void drawNodesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        int fontId = _openGraph_Cli.AddFont("Times New Roman", 30, 0, 0);
        Random random = new Random();

        const int nNodes = 400;
        const int nBatches = 10;
        const int nNodesPerBatch = nNodes / nBatches;
        for (int j = 0; j < nBatches; j++)
        {
            CColor_Cli color = new CColor_Cli((byte)(random.Next(0, 256)), 
                       (byte)(random.Next(0, 256)), (byte)(random.Next(0, 256)));
            CColor_Cli colorSelected = new CColor_Cli(255, 0, 0);
            List<CPointf_Cli> pPt = new List<CPointf_Cli>();
            List<int> pId = new List<int>();
            int nSize = (random.Next(0, 256) % 8);
            string strTag;
            strTag = string.Format("Tag_{0}", j);
            for (int i = 0; i < nNodesPerBatch; i++)
            {
                double x = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                double y = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                double z = ((double)random.Next(0, 256) / 256 - 0.5) * 4;
                string strText;
                int id = j * nNodesPerBatch + i + 1;
                strText = string.Format("{0}", id);
                if (i % 2 == 0)
                {
                    _openGraph_Cli.AddText(id, 0, new CPointf_Cli(x, y, z), 
                               strText, new CColor_Cli(125, 123, 0), colorSelected,
                               CTextAlignment_Cli.ALIGN_LEFT | 
                               CTextAlignment_Cli.ALIGN_BOTTOM, CStatus_Cli.STATUS_UNSELECTED);
                }
                else
                {
                    _openGraph_Cli.AddText(id, fontId, new CPointf_Cli(x, y, z), strText, 
                        new CColor_Cli(0, 123, 123), colorSelected,
                        CTextAlignment_Cli.ALIGN_LEFT | CTextAlignment_Cli.ALIGN_BOTTOM, 
                        CStatus_Cli.STATUS_UNSELECTED);
                }
                pId.Add(id);
                pPt.Add(new CPointf_Cli(x, y, z));
            }
            List<CIdentity_Cli> pIdentity = new List<CIdentity_Cli>();
            // for performance reason, you should send in your data in batches
            _openGraph_Cli.AddNodes(ref pIdentity, pId, pPt, nSize, color, 
                       colorSelected, CStatus_Cli.STATUS_UNSELECTED, strTag, null);
        }

        _openGraph_Cli.AddNode(99999, new CPointf_Cli(), 15, new CColor_Cli(255, 255, 0), 
                   new CColor_Cli(0, 255, 255), 0, null, null);
        _openGraph_Cli.AddText(99999, 0, new CPointf_Cli(), "99999", 
            new CColor_Cli(125, 123, 0), new CColor_Cli(0, 255, 255),
            CTextAlignment_Cli.ALIGN_LEFT | CTextAlignment_Cli.ALIGN_BOTTOM, 
            CStatus_Cli.STATUS_UNSELECTED);


        _openGraph_Cli.Render();
    }

    private void defaultToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeNONE);
    }

    private void trackToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeTRACK);
    }

    private void rotateToolStripMenuItem_Click(object sender, EventArgs e)
    {
        _openGraph_Cli.SetMode(COpenGraph_Cli.Mode.modeROTATE);
    }

    private void showAxesToolStripMenuItem_Click(object sender, EventArgs e)
    {
        m_bShowAxes = !m_bShowAxes;
        _openGraph_Cli.ShowAxes(m_bShowAxes);
        _openGraph_Cli.Render();
    }
}

Windows Presentation Foundation Form

For a WPF form, you can use the same WinForms user control inside the WPF WindowsFormsHost control. For example:

XML
<Window x:Class="OpenGraphWpfClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wf="clr-namespace:OpenGraphWpfClient"  
        Title="MainWindow" Height="520" Width="934" SizeChanged="Window_SizeChanged">
    <Grid>
        <WindowsFormsHost Margin="182,49,40,31" Name="windowsFormsHost1">
            <wf:OpenGLUserControl Name="openGLUserControl"></wf:OpenGLUserControl>
        </WindowsFormsHost>
    </Grid>
</Window>

Points of Interest

The code here may be augmented for actual production use. For example, you can add more interactivity to the code by allowing the user to zoom in, pan, or select individual objects. I leave those features to you to exercise your talents. You can also refer to www.cg-inc.com for help or more information. Thanks for reading and happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
President Computations & Graphics, Inc.
United States United States
Junlin Xu is the founder of Computations & Graphics, Inc. (http://www.cg-inc.com). He is the author of Real3D finite element package, SolverBlaze finite element SDK, OpenGraph Library (OpenGL-based visualization and charting SDK for native and .NET environment), and double128 SDK (quad precision floating point math for C++ and .NET).

Junlin has 20+ years software development experiences in various industries. He has skills in Windows desktop and web application development using C++, C++/CLI, C#, Qt, MFC, STL, OpenGL, GLSL, COM/COM+, WinForms, MS SQL, MySQL, ASP.NET and .NET Core, CSS, jQuery and jQuery UI, Autodesk Revit API, Inno Setup. He is also an expert in mathematical, CAD and algorithmic software development.

Comments and Discussions

 
Praiseexcellent! Pin
Southmountain23-Mar-21 7:53
Southmountain23-Mar-21 7:53 
GeneralMy vote of 5 Pin
code_junkie4-Mar-13 7:30
code_junkie4-Mar-13 7:30 
QuestionNot much use without the GL files Pin
Create an account dammit4-Mar-13 1:04
Create an account dammit4-Mar-13 1:04 
AnswerRe: Not much use without the GL files Pin
Junlin Xu4-Mar-13 2:46
Junlin Xu4-Mar-13 2:46 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA16-Feb-13 19:20
professionalȘtefan-Mihai MOGA16-Feb-13 19:20 
GeneralRe: My vote of 5 Pin
Junlin Xu18-Feb-13 4:46
Junlin Xu18-Feb-13 4:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.