Solved

How to create a user-defined control?

Posted on 1997-08-28
11
800 Views
Last Modified: 2013-11-20
I am using 1.52, creating a 16-bit app. I have a CProgressBar class defined as a sub-class of CWnd. When I want to place it in a "Work In Progress" dialog, I have to create it from within the OnCreate of the dialog.

I would like to make it a "user-defined" control, so that I can place it in the dialog in App Studio. Then the Create would be called automatically (not sure how, exactly).  

Here is a snipit of code to help explain what I am doing already:

"progress.h"

class CProgressBar : public CWnd
{
protected:
   COLORREF m_ProgressColor;
   COLORREF m_BackColor;
   COLORREF m_TextColor;
   UINT     m_Current;
   UINT     m_Total;

// Construction
public:
   BOOL PreCreateWindow(CREATESTRUCT& cs);

// Implementation
public:
   BOOL Create (const RECT& rect,
                CWnd* pParentWnd,
                UINT nID,
                COLORREF Background,
                COLORREF Foreground,
                COLORREF Text);
   void UpdateProgress (UINT Current,
                        UINT Total);
   
protected:
   // Generated message map functions
   //{{AFX_MSG(CProgressBar)
   afx_msg void OnPaint();
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()  
};

------------

"progress.cpp"

BEGIN_MESSAGE_MAP(CProgressBar, CWnd)
   //{{AFX_MSG_MAP(CProgressBar)
   ON_WM_PAINT()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CProgressBar::PreCreateWindow(CREATESTRUCT& cs)
{
   cs.lpszClass = AfxRegisterWndClass (WS_CHILD |                                        WS_VISIBLE);

   return TRUE;
}

///////////////////////////////////////////////////////////

BOOL CProgressBar::Create(const RECT& rect,
                          CWnd* pParentWnd,
                          UINT nID,
                          COLORREF Background,
                          COLORREF Foreground,
                          COLORREF Text)
{
   if (CWnd::Create ("CProgressBar",
                     "ProgressBar",
                     WS_CHILD | WS_VISIBLE,
                     rect,
                     pParentWnd,
                     nID) == 0)
   {
      return FALSE;
   }


   return TRUE;
}

///////////////////////////////////////////////////////////

void CProgressBar::UpdateProgress (UINT Current,
                                   UINT Total)
{
   // Set the percentage
}

////////////////////////////////////////////////////////

void CProgressBar::OnPaint()
{

 // Draw stuff...

}

--------------

Okay, then if I want to use it:

BOOL CMyDialog::Create(void)
{
   if (!CDialog::Create(CMyDialog::IDD))
   {
      return FALSE;
   }
   
   CRect ProgressRect;
   GetClientRect (&ProgressRect);
   CPoint BottomRight = ProgressRect.BottomRight();
   ProgressRect.SetRect (7, BottomRight.y - 33, BottomRight.x - 7, BottomRight.y - 7);

   if (!m_ProgressBar.Create (ProgressRect,
                              this,
                              IDC_PROGRESS_BAR_MY,
                              RGB(64, 128, 128),
                              RGB(0, 64, 128),
                              RGB(255,128,128)))
   {
      return FALSE;
   }

   return TRUE;
}                    

--------

The key is the PreCreateWindow function which registers the class to windows.  However, I am not sure how to register the class so that a "user-defined" control can use it. When I tried placing "CProgressBar" in for the class of the user defined control, I received a GPF, and the TRACE said that it couldn't find the window... or something like that.

Any help is appreciated.
0
Comment
Question by:deedge
11 Comments
 

Author Comment

by:deedge
ID: 1305072
Edited text of question
0
 
LVL 1

Expert Comment

by:kargo
ID: 1305073
If you want to make the control something that you can just drop-in you need to do a little work.  You need to go back to the Windows SDK days a little.  1st you need to register the window giving it a real name, not a pseudo name like MFC generates.
Once that is done you ca go into your dialog editor and add a user control.  The properties of the control have a Class Name entry which you should populate with the same name you used in the window registration.  The style value is a bitmask of the WS flags for the window, you have to manually calculate this.  Remember that if you create a DDX variable for the window you MUST use the class that you used when registering the window class, and not generic CWND or any other class.
0
 

Author Comment

by:deedge
ID: 1305074
Sorry Kargo, I think you tried answering my original question, rather than the edited version... could you (or someone) re-answer the question based on the code snipits above.
0
 
LVL 23

Expert Comment

by:chensu
ID: 1305075
CProgressBar::CProgressBar()
{
    WNDCLASS    wc;
   
    wc.hCursor          = ::LoadCursor(NULL, IDC_ARROW);
    wc.hIcon            = NULL;
    wc.lpszMenuName     = NULL;
    wc.lpszClassName    = "ProgressBar";
    wc.hbrBackground    = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hInstance        = ::AfxGetInstanceHandle();
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc      = ::DefWindowProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;

    ::RegisterClass(&wc);
}


In the dialog editor, create a custom control and specify the class name as "ProgressBar".

IDD_PROGDLG DIALOG PRELOAD FIXED PURE  144, 90, 241, 94
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Progress"
FONT 8, "Helv"
BEGIN
    PUSHBUTTON      "Cancel",IDCANCEL,101,75,41,14
    CONTROL         "",IDC_PROGBAR,"ProgressBar",0x7,8,51,225,14
    LTEXT           "",IDC_STATUS1,8,6,224,8
    LTEXT           "",IDC_STATUS2,8,19,224,8
    LTEXT           "",IDC_STATUS3,8,31,224,8
END


CMyDialog

CProgressBar m_progressbar;  // member of CMyDialog

Remove CProgressBar::PreCreateWindow and CProgressBar::Create. Remove the code for creating CProgressBar in CMyDialog::Create.

You may write a member function of CProgressBar to specify COLORREF Background,  COLORREF Foreground, COLORREF Text.
0
 

Author Comment

by:deedge
ID: 1305076
Again, sorry... it doesn't quite work correctly... I am able to register the class and place it with App Studio, and everything seems to work fine, except that my CProgressBar (defined as a subclass of a CWnd) doesn't receive any WM_PAINT messages... which is really wierd, because I ran Spy on the CProgressBar window in the dialog, and Spy reported that it was receiving WM_ERASEBKGRND and WM_PAINT messages all of the time (like I expected).  However, nothing happens (ie: OnPaint never gets called).  So, I tried calling GetClientRect() in the UpdateProgress routine, just to see what it was, and I received an Invalid Window Handle message everytime... I thought that was pretty strange, since the object itself is a subclass of a window, so I am not sure why it doesn't thing it has a handle. On top of that, Spy did think it had a handle, and it reported all of the stats like one would expect.  

It seems like I am not locking down something quite right.  Could you (or someone) give me that final push (or shove, by now).  Here is what I have:

-----------

class CProgressBar : public CWnd
{
protected:
   COLORREF m_ProgressColor;
   COLORREF m_BackColor;
   COLORREF m_TextColor;
   UINT     m_Current;
   UINT     m_Total;

// Construction
public:
   CProgressBar();

// Implementation
public:
   virtual ~CProgressBar();
   void SetColors (COLORREF Background,
                   COLORREF Foreground,
                   COLORREF Text);
   void UpdateProgress (UINT Current,
                        UINT Total);
   
protected:
   // Generated message map functions
   //{{AFX_MSG(CProgressBar)
   afx_msg void OnPaint();
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()  
};

-------------

// progress.cpp : implementation file
//

CProgressBar::CProgressBar()
{
   m_Current = 0;
   m_Total = 1;

   WNDCLASS wc;
   
   wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
   wc.hIcon = NULL;
   wc.lpszMenuName = NULL;
   wc.lpszClassName = "ProgressBar";
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wc.hInstance = ::AfxGetInstanceHandle();
   wc.style = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc = ::DefWindowProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   
   ::RegisterClass(&wc);
}

BEGIN_MESSAGE_MAP(CProgressBar, CWnd)
   //{{AFX_MSG_MAP(CProgressBar)
   ON_WM_PAINT()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CProgressBar::UpdateProgress (UINT Current,
                                   UINT Total)
{
 // update the prgress members...

   Invalidate(FALSE); // This call used to work, but now it
   // causes the whole screen to flicker (like I am invalidating
   // the desktop window or something.
}

void CProgressBar::OnPaint()
{
// This routine used to be called in response to the Invalidate
// or for any other WM_PAINT messages... now it is never called,
// even though 'Spy' says that the window is receiving the
// WM_PAINT messages.

   CPaintDC dc(this); // device context for painting
.
.
.

}

Okay, then the dialog that contains the CProgressBar object doesn't do anything really special.  I declare a member of m_ProgressBar, so the class gets registered on creation, and then I setcolors and updateprogress every once in a while... the .rc snipit is as follows:

IDD_DIALOG_WORK_IN_PROGRESS DIALOG DISCARDABLE  0, 0, 185, 66
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Title"
FONT 8, "MS Sans Serif"
BEGIN
    CTEXT           "Work In Progress... Please Wait",IDC_STATIC_MESSAGE,5,
                    15,175,8,NOT WS_GROUP
    CONTROL         "",IDC_PROGRESS_BAR_WIP,"ProgressBar",0x0,5,45,175,14
END

----------

I have tried changing the various values to match exactly what chensu had put down, but that didn't seem to help at all (not sure what the hex value is after the class name...)

anyway, Spy reported that I had a WS_CHILD window and the coordinates and everything...

Again, any help is much appreciated.
0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 

Author Comment

by:deedge
ID: 1305077
Something else I noticed was that if I have multiple dialog objects, I get a runtime trace error indicating that the class has already been registered.  Aside from making two different constructors, is there some way to avoid this problem... the only way I see is :

#ifndef __PBAR__
#define __PBAR__
...constructor1 with the registration...
#else
..constrictor1 without registration...
#endif

Perhaps there is some other "official windows" way to handle this problem.
0
 
LVL 23

Expert Comment

by:chensu
ID: 1305078
Try replacing
wndcls.lpfnWndProc = ::DefWindowProc;
with
wndcls.lpfnWndProc = ::AfxWndProc;
0
 

Author Comment

by:deedge
ID: 1305079
Nope, after making that change, I received a failed assertion in WinCore (line 210):

LRESULT CALLBACK AFX_EXPORT
AfxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   CWnd* pWnd;

   pWnd = CWnd::FromHandlePermanent(hWnd);
   ASSERT(pWnd != NULL);  // <- This is Line 210
   ASSERT(pWnd->m_hWnd == hWnd);

   LRESULT lResult = _AfxCallWndProc(pWnd, hWnd, message, wParam, lParam);

   return lResult;
}

While looking around in Wincore, I tried cut-n-pasting the body of AfxRegisterWndClass, because that is pretty much the same thing, but met with the same result. Any other ideas???  
0
 

Expert Comment

by:syjwg
ID: 1305080
Let MFC subclass the new control:
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMyDialog)
DDX_Control(pDX, IDC_PROGRESS_BAR_WIP, m_ProgressBar);
//}}AFX_DATA_MAP
}


0
 

Accepted Solution

by:
agopal earned 100 total points
ID: 1305081
I went thro' all the code snippets above .I find that the
requirement you have is nothing new .It is already in MFC
sample ctrltest (I think ) in file paredit2.cpp(may be changed now).
I have sucessfully used the ideas in the sample to create a
LOT of custom controls(as we call it) which are actively being used.

These are the changes you need to make to make it work fine.

First keep your Window registration information out of the CProgress constructor.Make a static function call it Init or
something and do the registration there and have the WNDCLASS
object declared in your class static too.

Next write your own WndProc which could be a static function of your class like this
 LRESULT CALLBACK AFX_EXPORT CProgressBar::MyWndProc(
 HWND hWnd ,UINT nMsg ,WPARAM wPar ,LPARAM lPar)
{
  // note that I assumed that the window regn info is out
  // of ther constructor and the static func that bears it has   // already been called.
  CProgressBar *pCPB = new CProgressBar(...whatever parameters)  
  // This step associates the object you just created with the     // Window.Now MFC could peacefully call your members without
  // crying.
   pCPB->Attach(hWnd);

  // this step resets the WndProc to AfxWndProc
 ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxWndProc);

 // This step calls the memberfunction for 'this' message
 // (nMsg is usually WM_NCCREATE right now so if there is an
     override for this message we need to call it)

#ifdef STRICT
return ::CallWindowProc(AfxWndProc, hWnd, nMsg,wPar,lPar);
#else
return ::CallWindowProc((FARPROC)AfxWndProc, hWnd, nMsg,      wPar,lPar);
#endif

  // That's it the next time ,if you have the message map
  // your member function will get called
}


Last thing you need to change is the WNDCLASS info to take MyWndProc
          wndcls.lpfnWndProc = CProgressBar::MyWndProc;

Presto! there is your user-defined control code.







0
 

Author Comment

by:deedge
ID: 1305082
Yeah, I already saw that sample code... it may work, but I finally gave up on trying to do this, because (for what I am trying to do) it is much easier (and less code) to do what I was doing to begin with.  However, for anyone else who wants the answer to this question, I think that looking at the sample code mentioned is the correct answer.
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Here is how to use MFC's automatic Radio Button handling in your dialog boxes and forms.  Beginner programmers usually start with a OnClick handler for each radio button and that's just not the right way to go.  MFC has a very cool system for handli…
Introduction: The undo support, implementing a stack. Continuing from the eigth article about sudoku.   We need a mechanism to keep track of the digits entered so as to implement an undo mechanism.  This should be a ‘Last In First Out’ collec…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Sending a Secure fax is easy with eFax Corporate (http://www.enterprise.efax.com). First, Just open a new email message.  In the To field, type your recipient's fax number @efaxsend.com. You can even send a secure international fax — just include t…

705 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now