Click here to Skip to main content
Click here to Skip to main content
Articles » Database » Database » General » Downloads
 
Add your own
alternative version

Database Visualization

, 31 May 2006
This article aims to create a simple tool for visualizing database tables and relations, a database map to refer to.
src.zip
src
msado15.tlh
msado15.tli
QueryBuilder.dsp
QueryBuilder.dsw
QueryBuilder.exe
QueryBuilder.clw
res
QueryBuilder.ico
logo.bmp
msado15.tlh
msado15.tli
Manifest
default1.bin
ListIcons.bmp
wingraphviz.zip
WinGraphViz
WinGraphviz_v1.02.25s.cab
WinGraphviz_v1.02.24.msi
WinGraphviz_v1.02.24.cab
WinGraphviz_v1.02.20.msi
WinGraphviz_v1.02.20.cab
dotguide.pdf
#include "stdafx.h"
#include "resource.h"
#include "ListboxEx.h"

// Global variables.
static unsigned int const g_DragListMsg = RegisterWindowMessage( DRAGLISTMSGSTRING );

#ifdef _DEBUG
#  define new DEBUG_NEW
#  undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
#endif

// Local defines
#define ALT_KEY_PRESSED( uFlag )  ( ((uFlag) & (1 << 3)) != 0 )
#define LBEX_LASTITEM_MAGIC     0x45424558   // 'LBEX'

BEGIN_MESSAGE_MAP( CListBoxEx, CDragListBox )
   //{{AFX_MSG_MAP(CListBoxEx)
   ON_WM_CREATE()
   ON_WM_LBUTTONDOWN()
   ON_WM_LBUTTONDBLCLK()
   ON_WM_LBUTTONUP()
   ON_WM_SYSKEYDOWN()
   ON_WM_KEYDOWN()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNCREATE( CListBoxEx, CDragListBox )


CListBoxEx::CListBoxEx()
{
   m_pBuddy = NULL; 
   m_iSelected = -1;
   m_bAllowDrag = TRUE;
}

CListBoxEx::~CListBoxEx(){}

BOOL CListBoxEx::PreCreateWindow( CREATESTRUCT & cs ) 
{
   cs.style |= LBEX_STYLE;
   cs.dwExStyle |= LBEX_EXSTYLE;
   return CWnd::PreCreateWindow(cs);
}

void CListBoxEx::PreSubclassWindow() 
{
   ModifyStyle( 0, LBEX_STYLE, SWP_SHOWWINDOW );
   ModifyStyleEx( 0, LBEX_EXSTYLE, SWP_SHOWWINDOW );
   CDragListBox::PreSubclassWindow();
}

int CListBoxEx::OnCreate( LPCREATESTRUCT lpCreateStruct ) 
{
   if ( CDragListBox::OnCreate(lpCreateStruct) == -1 )
      return -1;
    
   return 0;
}

//             Draw functions                //
// DESCRIPTION:   Called by the framework when a list box with an owner-draw style is created. 
void CListBoxEx::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
   // Get current font metrics
   TEXTMETRIC metrics;
   HDC dc = ::GetDC( m_hWnd );
   GetTextMetrics( dc, &metrics );
   ::ReleaseDC( m_hWnd, dc );
   
   // Set the height
   lpMeasureItemStruct->itemHeight = metrics.tmHeight + metrics.tmExternalLeading;
}


// DESCRIPTION:   Called by the framework when a visual aspect of an owner-draw list box changes.  
void CListBoxEx::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
   // If there are no list box items, skip this message. 
   if ( lpDrawItemStruct->itemID == -1 )
      return; 
   
   CString  strItemText;
   CRect    rcText( lpDrawItemStruct->rcItem );

   COLORREF clrItemText,clrOldTextColor;

   // Put a bit of room to the left of the text
   rcText.left += 8;

   // Act upon the item state
   switch ( lpDrawItemStruct->itemAction )
   { 
      case ODA_SELECT: 
      case ODA_DRAWENTIRE: 
      
         // Is the item selected? 
         if ( lpDrawItemStruct->itemState & ODS_SELECTED ) 
         { 
            clrItemText = GetSysColor( COLOR_HIGHLIGHTTEXT );
            // Clear the rectangle
            FillRect(  lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)(COLOR_ACTIVECAPTION+1) );
         } 
         else
         { 
            clrItemText = GetSysColor( COLOR_WINDOWTEXT );
            // Clear the rectangle
            FillRect(  lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, (HBRUSH)(COLOR_WINDOW+1) );
         } 

         clrOldTextColor = SetTextColor( lpDrawItemStruct->hDC,clrItemText );
         SetBkMode( lpDrawItemStruct->hDC,TRANSPARENT );

         // Display the text associated with the item. 
         if ( lpDrawItemStruct->itemData != LBEX_LASTITEM_MAGIC )
         {
            GetText( lpDrawItemStruct->itemID,strItemText );
            DrawText(  lpDrawItemStruct->hDC,LPCTSTR( strItemText ),strItemText.GetLength(),
                      &rcText,DT_SINGLELINE | DT_VCENTER );
         }
         else
         {
            DrawText(  lpDrawItemStruct->hDC,"--- Last Item",strItemText.GetLength(),
                      &rcText,DT_SINGLELINE | DT_VCENTER );
         }
         // Is the item selected? 
         if ( lpDrawItemStruct->itemState & ODS_SELECTED ) 
         { 
            SetTextColor( lpDrawItemStruct->hDC,clrOldTextColor );
            DrawFocusRect(  lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem ); 
         } 

         break; 
   }
}


// DESCRIPTION:   Draws the insertion guide before the item with the indicated index. 
void CListBoxEx::DrawSeparator( int nIndex )
{
   if ( nIndex == -1 )
      return;

   CBrush* pBrush = CDC::GetHalftoneBrush();
   
   CRect rect;
   GetClientRect(&rect);
   
   CRgn rgn;
   rgn.CreateRectRgnIndirect( &rect );
   
   CDC* pDC = GetDC();
   
   // Prevent drawing outside of listbox
   // This can happen at the top of the listbox since the listbox's DC is the parent's DC
   pDC->SelectClipRgn( &rgn );
   
   GetItemRect( nIndex, &rect );
   rect.bottom = rect.top+2;
   rect.top -= 2;
   rect.left += 5;
   rect.right -= 5;
   CBrush* pBrushOld = pDC->SelectObject(pBrush);

   // Draw main line
   pDC->PatBlt( rect.left, rect.top, rect.Width(), rect.Height(), PATINVERT );
   
   // Draw vertical lines
   pDC->PatBlt( rect.left-3, rect.top-4, 3, rect.Height()+8, PATINVERT );
   pDC->PatBlt( rect.right, rect.top-4, 3, rect.Height()+8, PATINVERT );
   
   pDC->SelectObject( pBrushOld );
   ReleaseDC( pDC );
}


// DESCRIPTION:   Called to draw the insertion guide before the item with the indicated index. 
void CListBoxEx::DrawInsert( int nIndex )
{
   if ( m_nLast != nIndex )
   {
      DrawSeparator( m_nLast );
      DrawSeparator( nIndex );
   }
   // Set last selected
   m_nLast = nIndex;
}

//                 Messages                  //

BOOL CListBoxEx::OnChildNotify( UINT nMessage, WPARAM wParam, LPARAM lParam, LRESULT *pLResult )
{
   if ( nMessage == g_DragListMsg )
   {
      ASSERT( pLResult != NULL );
      if (m_bAllowDrag )
      {
         LPDRAGLISTINFO pInfo = (LPDRAGLISTINFO)lParam;
         ASSERT( pInfo != NULL );
         switch ( pInfo->uNotification )
         {
            case DL_BEGINDRAG:
               TRACE( "Begin Dragging\n" );
               // Removed from the MFC implementation
               //*pLResult = BeginDrag(pInfo->ptCursor);
               *pLResult = TRUE;
               break;

            case DL_CANCELDRAG:
               TRACE( "Cancel Drag\n" );
               CancelDrag( pInfo->ptCursor );
               break;

            case DL_DRAGGING:
               TRACE( "Dragging\n" );
               *pLResult = Dragging( pInfo->ptCursor );
               break;

            case DL_DROPPED:
               TRACE( "Dropped\n" );
               Dropped( GetCurSel(), pInfo->ptCursor );
               break;
         }
      }
      else
         *pLResult = FALSE;

      return TRUE;
   }

   return CListBox::OnChildNotify( nMessage, wParam, lParam, pLResult );
}


void CListBoxEx::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
   CDragListBox::OnKeyDown( nChar, nRepCnt, nFlags );

   if ( (nChar == VK_DELETE) && (m_iSelected != -1) )
      DeleteString( m_iSelected );
}


void CListBoxEx::OnSysKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
   if ( ALT_KEY_PRESSED( nFlags ) && m_bAllowDrag )
   {
      switch ( nChar )
      {
         case VK_UP:
         case VK_LEFT:

            MoveItemUp( GetCurSel() );
            break;

         case VK_DOWN:
         case VK_RIGHT:

            MoveItemDown( GetCurSel() );
            break;
      }
   }

   CDragListBox::OnSysKeyDown( nChar, nRepCnt, nFlags );
}

void CListBoxEx::OnLButtonDown( UINT   nFlags, CPoint point )
{
   int     iItem;
   
   ClientToScreen( &point );
   iItem = ItemFromPt( point, FALSE );
   TRACE1( "LButtonDown: %d\n", iItem );

   if ( iItem != -1 )
   {
      if ( m_iSelected != iItem )
      {
         // Update info
         m_iSelected = iItem;
      }
   }

   CDragListBox::OnLButtonDown( nFlags, point );
}

void CListBoxEx::OnLButtonDblClk( UINT   nFlags, CPoint point )
{
   CDragListBox::OnLButtonDblClk( nFlags, point );
}


void CListBoxEx::OnLButtonUp( UINT   nFlags, CPoint point )
{
   CDragListBox::OnLButtonUp( nFlags, point );
}

//             Public functions              //

// DESCRIPTION:   Set item text and data
void CListBoxEx::SetItem( int     iItem, LPCTSTR szItemText, DWORD   dwItemData )
{
   ASSERT( iItem < GetCount() );

   SendMessage( WM_SETREDRAW, FALSE, 0 );

   DeleteString( iItem );
   InsertString( iItem, szItemText );
   SetItemData( iItem, dwItemData );

   SendMessage( WM_SETREDRAW, TRUE, 0 );
}

void CListBoxEx::SetItemText( int     iItem, LPCTSTR szItemText )
{
   ASSERT( iItem < GetCount() );
   DWORD dwItemData;
   dwItemData = GetItemData( iItem );
   SetItem( iItem, szItemText, dwItemData );
}

// DESCRIPTION:   Called to swap the two items 
void CListBoxEx::SwapItems( int iFirstItem, int iSecondItem )
{
   ASSERT( iFirstItem < GetCount() );
   ASSERT( iSecondItem < GetCount() );
   
   if ( iFirstItem != iSecondItem )
   {
      // Cache the first item data
      CString strFirstItem;
      DWORD dwFirstItemData;
      
      GetText( iFirstItem, strFirstItem );
      dwFirstItemData = GetItemData( iFirstItem );

      // Cache the second item data
      CString strSecondItem;
      DWORD dwSecondItemData;
      
      GetText( iSecondItem, strSecondItem );
      dwSecondItemData = GetItemData( iSecondItem );

      // Insert the items in reverse order
      if ( iFirstItem < iSecondItem )
      {
         SetItem( iFirstItem, strSecondItem, dwSecondItemData );
         SetItem( iSecondItem, strFirstItem, dwFirstItemData );
      }
      else
      {
         SetItem( iSecondItem, strFirstItem, dwFirstItemData );
         SetItem( iFirstItem, strSecondItem, dwSecondItemData );
      }
   }
}

int CListBoxEx::MoveItemUp( int iItem )
{
   ASSERT( iItem > 0 );

   if ( iItem > 0 )
   {
      SwapItems( iItem, iItem-1 );
      SetCurSel( iItem - 1 );
   }
   return iItem;
}

int CListBoxEx::MoveItemDown( int iItem )
{
   ASSERT( iItem >= 0 );

   if ( iItem != GetCount()-1 )
   {
      SwapItems( iItem, iItem+1 );
      SetCurSel( iItem + 1 );
   }
   return iItem;
}

//                       CListBoxExBuddy class                              //

BEGIN_MESSAGE_MAP(CListBoxExBuddy, CWnd)
   //{{AFX_MSG_MAP(CListBoxExBuddy)
   ON_WM_PAINT()
   ON_WM_MOUSEMOVE()
   ON_WM_LBUTTONDOWN()
   ON_WM_LBUTTONUP()
   ON_WM_SIZE()
   ON_WM_NCMOUSEMOVE()
	ON_WM_CREATE()
	//}}AFX_MSG_MAP
   //ON_NOTIFY( TTN_NEEDTEXT, LBB_TTIP_ID, OnTooltipNeedText )
END_MESSAGE_MAP()

CListBoxExBuddy::CListBoxExBuddy()
{
	m_ButtonBitmap.LoadBitmap(IDB_BMP_LIST_ICONS);
	
   // Init other data
   m_bButtonPressed = FALSE;
   m_iButton = __BMP_NUMBTN;
   m_pListBoxEx = NULL;

   m_arcButtons = new CRect[ __BMP_NUMBTN ];

#ifdef _DEBUG
   // Verify the dimensions
   BITMAP BmpInfo;
   m_ButtonBitmap.GetObject( sizeof(BITMAP), &BmpInfo );
   ASSERT( BmpInfo.bmWidth == __BMP_WIDTH );
   ASSERT( BmpInfo.bmHeight == __BMP_HEIGHT );
#endif
}

CListBoxExBuddy::~CListBoxExBuddy()
{
   delete[] m_arcButtons;
}

//             Initialization                //
int CListBoxExBuddy::OnCreate( LPCREATESTRUCT lpCreateStruct ) 
{
	if ( CWnd::OnCreate(lpCreateStruct) == -1 )
		return -1;
   
	CreateTooltips();
	return 0;
}

void CListBoxExBuddy::PreSubclassWindow() 
{
	CreateTooltips();

	// Send a WM_SIZE message, as WM_CREATE would do
	CRect rcClient;
	GetClientRect( &rcClient );
	OnSize( 0, rcClient.Width(), rcClient.Height() );

	CWnd::PreSubclassWindow();
}

static void DrawBitmap(CDC & dc,CBitmap & bmp,int x,int y,int cx,int cy)
{
   CDC memDC;
   memDC.CreateCompatibleDC( &dc );

   CBitmap *poldbmp = memDC.SelectObject(&bmp);
   
   dc.BitBlt(x, y, cx, cy, &memDC,0, 0, SRCCOPY);
   
   memDC.SelectObject( poldbmp );
   memDC.DeleteDC();
}

void CListBoxExBuddy::OnPaint() 
{
   CPaintDC dc( this ); // device context for painting

   // Create a compatible memory DC
   CDC memDC;
   memDC.CreateCompatibleDC( &dc );

   // Get aware of the size of the client area
   CRect rcClient;
   GetClientRect( &rcClient );

   // This is used to center the button bitmap
   int nBmpTopY = (rcClient.Height() - __BMP_HEIGHT) / 2;

   // To store old selected objects
   CBitmap *pOldBmp;
   CFont   *pOldFont;

   // Select the font
   CFont font;
   font.Attach( (HFONT)GetStockObject( DEFAULT_GUI_FONT ) );
   pOldFont = memDC.SelectObject( &font );

   // Select the out-of-screen bitmap
   CBitmap memBmp;
   memBmp.CreateCompatibleBitmap( &dc,rcClient.Width(),rcClient.Height() );
   pOldBmp = memDC.SelectObject( &memBmp );

   // Erase the background
   CBrush brush;
   brush.CreateSolidBrush( ::GetSysColor(COLOR_3DFACE) );
   memDC.FillRect( &rcClient, &brush );
   brush.DeleteObject();

   // Prepare to draw the text transparently
   memDC.SetBkMode( TRANSPARENT );
   memDC.SetTextColor( ::GetSysColor(COLOR_WINDOWTEXT) );

   // Draw the text
   CString strWindowText;
   GetWindowText( strWindowText );
   memDC.DrawText( strWindowText, rcClient, DT_SINGLELINE | DT_VCENTER );

   // Draw the button bitmap
   DrawBitmap( memDC,m_ButtonBitmap, rcClient.right - __BMP_WIDTH - 2,nBmpTopY,__BMP_WIDTH,__BMP_HEIGHT );

   // Draw the button edge
   if ( m_iButton != __BMP_NUMBTN)
   {
      CRect rcButtonEdge( rcClient.right - (__BMP_NUMBTN - m_iButton)*__BMP_BTNWID - 2,
                          nBmpTopY,
                          rcClient.right - (__BMP_NUMBTN - m_iButton - 1)*__BMP_BTNWID - 2,
                          __BMP_HEIGHT + nBmpTopY );
      memDC.DrawEdge( &rcButtonEdge, m_bButtonPressed ? BDR_SUNKENOUTER : BDR_RAISEDINNER, BF_RECT );
   }

   dc.BitBlt(2, 0, rcClient.Width()-2, rcClient.Height(), &memDC, 0, 0, SRCCOPY);

   memDC.SelectObject( pOldBmp );

   // Select the font out of the device context
   memDC.SelectObject( pOldFont );
}


// DESCRIPTION:   Finds a button given a point.
int CListBoxExBuddy::FindButton( const CPoint & point )
{
   // Find the button
   for ( UINT iIndex = 0; iIndex < __BMP_NUMBTN; iIndex++ )
   {
      if ( m_arcButtons[iIndex].PtInRect( point ) )
         break;
   }

   return iIndex;
}

                                  
// DESCRIPTION:   Called to redraw a button.
void CListBoxExBuddy::InvalidateButton(int  iIndex, BOOL bUpdateWindow /*= TRUE */)
{
   if ( iIndex < __BMP_NUMBTN && iIndex !=0)
       InvalidateRect( &m_arcButtons[ iIndex ], FALSE );
 
   if ( bUpdateWindow )
	   UpdateWindow();
}


void CListBoxExBuddy::OnMouseMove( UINT   nFlags, CPoint point ) 
{
   if ( !m_bButtonPressed )
   {
      UINT iIndex = FindButton( point );

      // If found a button, update info
      if ( iIndex != m_iButton && iIndex !=0)
      {
         InvalidateButton( m_iButton, FALSE );
         m_iButton = iIndex;
         InvalidateButton( m_iButton, TRUE );
      }
   }

   CWnd::OnMouseMove(nFlags, point);
}

void CListBoxExBuddy::OnLButtonDown( UINT   nFlags,CPoint point )
{
   // Capture the mouse
   SetCapture();

   // Find the button
   m_iButton = FindButton( point );

   // Redraw the button
   if ( m_iButton != __BMP_NUMBTN )
   {
      m_bButtonPressed = TRUE;

      // Redraw only the affected button
      InvalidateRect( &m_arcButtons[ m_iButton ], FALSE );
      UpdateWindow();
   }

   CWnd::OnLButtonDown(nFlags, point);
}


void CListBoxExBuddy::OnLButtonUp( UINT   nFlags,CPoint point )
{
   // Find the button
   UINT iButton = FindButton( point );

   // Accept only clicks that occur on the same button where the mouse was pressed
   if ( iButton == m_iButton )
   {
      // Take action, if necessary
      if ( m_iButton != __BMP_NUMBTN)
         DoClick( m_iButton );
   }

   // Set default conditions
   m_bButtonPressed = FALSE;

   // Redraw
   Invalidate( FALSE );

   // Memorize last
   m_iButton = iButton;

   // Release mouse capture
   ReleaseCapture();

   // Call base
   CWnd::OnLButtonUp(nFlags, point);
}

// DESCRIPTION:   Called when a click occurs on one of the action button.
void CListBoxExBuddy::DoClick( int iIndex )
{
   int iSelected = m_pListBoxEx->GetCurSel();

   switch ( iIndex )
   {
      case __BTN_DEL:

         if ( iSelected != -1 ) m_pListBoxEx->DeleteString( iSelected );
         break;

      case __BTN_UP:

         if ( iSelected != -1 ) m_pListBoxEx->MoveItemUp( iSelected );
         break;

      case __BTN_DOWN:

         if ( iSelected != -1 ) m_pListBoxEx->MoveItemDown( m_pListBoxEx->GetCurSel() );
         break;
   }
}

void CListBoxExBuddy::OnNcMouseMove( UINT   nHitTest, CPoint point ) 
{
   // Redraw the affected button
   InvalidateButton( m_iButton, FALSE );
   m_iButton = FindButton( point );

   InvalidateButton( m_iButton, TRUE );

   // Call base
   CWnd::OnNcMouseMove(nHitTest, point);
}

//            Tooltip Management             //

void CListBoxExBuddy::CreateTooltips()
{
   m_ToolTip.Create( this );

   // Set tip common data
   TOOLINFO ttInfo;
   ttInfo.cbSize   = sizeof( TOOLINFO );
   ttInfo.uFlags   = TTF_SUBCLASS;
   ttInfo.hwnd     = m_hWnd;
   ttInfo.rect     = CRect( 0, 0, 0, 0 ); // OnSize will resize it
   ttInfo.hinst    = NULL;
   ttInfo.lpszText = LPSTR_TEXTCALLBACK;
   ttInfo.lParam   = 0;

   // Add tooltips for each button
   for ( int iTip = 0; iTip < __BMP_NUMBTN; iTip++ )
   {
      ttInfo.uId = iTip+1;
      m_ToolTip.SendMessage( TTM_ADDTOOL, 0, (LPARAM)&ttInfo );
      m_ToolTip.Activate( TRUE );
   }
}

void CListBoxExBuddy::SetTipText( UINT   nID, LPTSTR szTipText )
{
   TCHAR *aszTips[] = { _T("Delete"),
                        _T("Move Up"),
                        _T("Move Down") };

   // Set tooltip text
   if (nID < __BMP_NUMBTN)
      _tcscpy( szTipText, aszTips[ nID ] );
}

void CListBoxExBuddy::OnSize( UINT nType, int  cx, int  cy )
{
   // Get aware of the size of the client area
   CRect rcClient;
   GetClientRect( &rcClient );

   // This is used to center the button bitmap
   int nBmpTopY = (rcClient.Height() - __BMP_HEIGHT) / 2;

   // Update buttons positions
   TOOLINFO ttInfo;
   for ( int iIndex = 0; iIndex < __BMP_NUMBTN; iIndex++ )
   {
      m_arcButtons[ iIndex ].top     = nBmpTopY;
      m_arcButtons[ iIndex ].left    = cx - (__BMP_NUMBTN-iIndex)*__BMP_BTNWID;
      m_arcButtons[ iIndex ].bottom  = __BMP_HEIGHT + nBmpTopY;
      m_arcButtons[ iIndex ].right   = cx - (__BMP_NUMBTN-iIndex-1)*__BMP_BTNWID;

      // Resize tooltip area
      ttInfo.cbSize   = sizeof( TOOLINFO );
      ttInfo.hwnd     = m_hWnd;
      ttInfo.uId      = iIndex+1;
      ttInfo.rect     = m_arcButtons[ iIndex ];
      m_ToolTip.SendMessage( TTM_NEWTOOLRECT, 0, (LPARAM)&ttInfo );
   }

   // Call base
   CWnd::OnSize( nType, cx, cy );
}

BOOL CListBoxExBuddy::OnNotify( WPARAM   wParam, LPARAM   lParam, LRESULT *pResult )
{
   UINT nCode = ((NMHDR *)lParam)->code;

   // Get tooltip notification
   if ( nCode == TTN_GETDISPINFO )
   {
      UINT nID = ((NMHDR *)lParam)->idFrom - 1;
      SetTipText( nID, 
                  ((NMTTDISPINFO *)lParam)->szText );
      return TRUE;
   }

   // Call base
   return CWnd::OnNotify( wParam, lParam, pResult );
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

VGirish
Founder
India India
No Biography provided

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 31 May 2006
Article Copyright 2006 by VGirish
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid