Solved

CFileDialog derived class and colored buttons in it

Posted on 2000-03-01
15
603 Views
Last Modified: 2013-11-20
Hi guys!
Here I go again, this time with a very interesting question. I have a class derived from CFileDialog. I wanted to customize an open file dialog box so that the “Open” and “Cancel” buttons are colored. In order to do that I used a class implemented by Bob Ryan which can be found at: http://www.codeguru.com/buttonctrl/color_button.shtml .
This class is very simple. Derived from CButton, the CColorButton is declared like this:

class CColorButton : public CButton
{
DECLARE_DYNAMIC(CColorButton)
public:
   CColorButton();
   virtual ~CColorButton();

   BOOL Attach(const UINT nID,
               CWnd* pParent,
               const COLORREF BGColor = RGB(192, 192, 192), // gray button
               const COLORREF FGColor = RGB(1, 1, 1), // black text
               const COLORREF DisabledColor = RGB(128, 128, 128),      // dark gray disabled text
               const UINT nBevel = 2);
      
protected:
   virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
   void DrawFrame(CDC *DC, CRect R, int Inset);
   void DrawFilledRect(CDC *DC, CRect R, COLORREF color);
   void DrawLine(CDC *DC, CRect EndPoints, COLORREF color);
   void DrawLine(CDC *DC, long left, long top, long right, long bottom, COLORREF color);
   void DrawButtonText(CDC *DC, CRect R, const _TCHAR *Buf, COLORREF TextColor);

   COLORREF GetFGColor() {return m_fg;}      
   COLORREF GetBGColor() {return m_bg;}
   COLORREF GetDisabledColor() {return m_disabled;}
   UINT GetBevel() {return m_bevel;}

   DECLARE_MESSAGE_MAP()

private:
   COLORREF m_fg, m_bg, m_disabled;
   UINT m_bevel;
};

The most important member is CColorButton::Attach which is implemented like this:

BOOL CColorButton::Attach( const UINT nID,
                           CWnd* pParent,
                           const COLORREF BGColor,
                           const COLORREF FGColor,
                           const COLORREF DisabledColor,
                           const UINT nBevel)
{
   if (!SubclassDlgItem(nID, pParent))
            return FALSE;

   m_fg = FGColor;
   m_bg = BGColor;
   m_disabled = DisabledColor;
   m_bevel = nBevel;

   return TRUE;
}

My class, derived from CFileDialog, implements the OnInitDialog function this way:

BOOL CMyFileDialog::OnInitDialog()
{
   CFileDialog::OnInitDialog();

   CWnd *pWindowDlg = GetParent();

   VERIFY(m_btnOK.Attach(IDOK, pWindowDlg, CYAN, BLACK, LTGRAY));
   VERIFY(m_btnCancel.Attach(IDCANCEL, pWindowDlg, BLUE, WHITE, DKGRAY));

   m_btnOK.SetButtonStyle( BS_OWNERDRAW, TRUE );
   m_btnCancel.SetButtonStyle( BS_OWNERDRAW, TRUE );

   return TRUE;
}

where m_btnOK and m_btnCancel are of CColorButton type.

As Bob Ryan says is his article, a color button must have the Owner Draw style in order to be redrawn in color. That’s way a have to set that style to my buttons too. Here starts my problem. If I keep the two instructions in my OnInitDialog function, my dialog crashes when it gets created. If I comment out those two lines:

// m_btnOK.SetButtonStyle( BS_OWNERDRAW, TRUE );
// m_btnCancel.SetButtonStyle( BS_OWNERDRAW, TRUE );

my dialog is displayed but the buttons are not colored.
I’d like to know what’s going on! I do not accept solutions like deriving a separate class directly from the dialog template for CFileDialog.
Regards,
Mensana

0
Comment
Question by:Mensana
  • 6
  • 5
  • 4
15 Comments
 
LVL 30

Expert Comment

by:SteveGTR
ID: 2574323
I think you want to pass a pointer to your dialog when calling CColorButton's Attach function. Like so:

VERIFY(m_btnOK.Attach(IDOK, this, CYAN, BLACK, LTGRAY));
VERIFY(m_btnCancel.Attach(IDCANCEL, this, BLUE, WHITE, DKGRAY));

Good Luck,
Steve
0
 
LVL 1

Author Comment

by:Mensana
ID: 2576669
No, no! You’re wrong. The “Open” and “Cancel” buttons belongs to CFileDialog class (behind the IDOK and IDCANCEL IDs). When you want to attach a CColorButton object to a button on the screen, you have to subclass the dialog item by passing a pointer to the parent window, in our case CFileDialog. CMyFileDialog is derived from CFileDialog, that's why I have the statement:

CWnd *pWindowDlg = GetParent();

and the pass the pWindowDlg pointer to the Attach function.

The code is fine from this point of view. I told everybody that the code works fine when I comment out those two lines that set the OwnerDraw style for each button. The problem is not inside the Attach function but somewhere else.
Sorry, but I have to reject your answer.
Mensana
0
 
LVL 30

Expert Comment

by:SteveGTR
ID: 2577120
Try it... I think you'll find it works.

"When you want to attach a CColorButton object to a button on the screen, you have to subclass the dialog item by passing a pointer to the parent window, in our case CFileDialog. CMyFileDialog is derived from CFileDialog, that's why I have the statement:"

Deriving from CFileDialog doesn't mean that CFileDialog is the parent. You have one window. For instance CButton is derived from CWnd, but calling GetParent() from CButton code won't return a pointer to the CWnd.

0
 
LVL 1

Author Comment

by:Mensana
ID: 2577298
Trust me, I tried it. Actually that’s how I originally implemented my OnInitDialog. It simply crashed. At some point, inside SetButtonStyle I get an assertion because m_hWnd was NULL. That means that the Attach function did not work properly. Then I read Christian Skovdal Andersen’s article that can be found at: http://www.codeguru.com/dialog/cmdlg.shtml
And there I saw that in fact I should not use GetDlgItem(IDOK) to get a pointer to the “Open” button but SubclassDlgItem..
Well you’re right. I shouldn’t use the “parent” terminology. That might be confusing but this is what Christian used in his article.
On the other hand, according to the VC++ documentation, CWnd::GetParent always return a pointer to a CWnd object. The question is: What object?
0
 

Expert Comment

by:joet1989
ID: 2577959
I think your problem has to do with the value of the button style before you set it.  The value of the style of the OK button is BS_DEFPUSHBUTTON (or 1).  Try the code below to get it to work.

int nButtonStyle;

nButtonStyle = m_btnOK.GetButtonStyle();
m_btnOK.SetButtonStyle( nButtonStyle & BS_OWNERDRAW, TRUE );
nButtonStyle = m_btnCancel.GetButtonStyle();
m_btnCancel.SetButtonStyle( nButtonStyle & BS_OWNERDRAW, TRUE );


I did not try to make the color change.  However, just for the fun of it, I added the following code to make sure that I have the button.  The following code removes the buttons from the screen.

m_btnOK.ShowWindow(SW_HIDE);
m_btnCancel.ShowWindow(SW_HIDE);

Good Luck...

0
 
LVL 1

Author Comment

by:Mensana
ID: 2578145
For joet1989
Sorry, but it makes no sense. The BS_OWNERDRAW style is defined as 0x0000000BL whereas BS_DEFPUSHBUTTON is 0x00000001L. The style of the OK button is indeed BS_DEFPUSHBUTTON. The Cancel button has no style assigned. By doing a bitwise-AND (&) you only preserve the BS_DEFPUSHBUTTON style. Coding like you suggested, the application doesn’t crash anymore but the buttons don’t get colored and that’s because they don’t have the Owner Draw style. I guess you thought to a bitwise-inclusive-OR (|) which would make more sense:

UINT nButtonStyle;

nButtonStyle = m_btnOK.GetButtonStyle();
m_btnOK.SetButtonStyle( nButtonStyle | BS_OWNERDRAW, TRUE );
nButtonStyle = m_btnCancel.GetButtonStyle();
m_btnCancel.SetButtonStyle( nButtonStyle | BS_OWNERDRAW, TRUE );

Having coded like this, the application keeps crashing. When I try to debug, the stack shows me more than 20 calls traced inside some ASM code. It’s hard tell what’s up!
Thanks for trying to help me anyway!
M.
0
 
LVL 30

Expert Comment

by:SteveGTR
ID: 2578312
I tried what you are trying with the same results. Here is something interesting though. If you turn off OFN_EXPLORER flag and pass "this" (instead of GetParent()) to the Attach function it works. But, you get the old style open box:

  dlg.m_ofn.Flags &= ~OFN_EXPLORER;
  dlg.DoModal();

0
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.

 

Expert Comment

by:joet1989
ID: 2578442
Oops.  You were right about the AND vs OR.  Very interesting problem.  
0
 
LVL 1

Author Comment

by:Mensana
ID: 2580366
Adjusted points to 150
0
 
LVL 1

Author Comment

by:Mensana
ID: 2580367
For SteveGTR

OK, that’s an improvement. It did work. But I still want to have the latest style open box. I increase the points to 150, as it seems that this is not an easy problem.
Where are you, .B ekiM?

PS: Just my curiosity: SteveGTR has something to do with Steve Howe and Steve Hackett?

0
 
LVL 30

Accepted Solution

by:
SteveGTR earned 150 total points
ID: 2581205
Ok, I figured it out. The problem is that the IDOK and IDCANCEL buttons on the open dialog don't know how to draw itself and MFC isn't passing the message on to your button control. Here is rough code that will allow you to do what you want for the IDOK. It can be cleaned and also applied to the IDCANCEL button:

// Global variables and functions
// Could be incorporated in your class
static WNDPROC oldWndProc;
static CColorButton* pOkButton;

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
  if (nMsg == WM_DRAWITEM && wParam == IDOK)
    {
    pOkButton->SendMessage(WM_DRAWITEM, wParam, lParam); //DrawItem((LPDRAWITEMSTRUCT) lParam);
    return 1L;
    }

 return CallWindowProc(oldWndProc, hWnd, nMsg, wParam, lParam);
}

// Your CFileDialog derived class
BOOL CMyFileDialog::OnInitDialog()
{
  CFileDialog::OnInitDialog();

  CWnd *pWindowDlg = GetParent();

  VERIFY(m_btnOK.Attach(IDOK, pWindowDlg, CYAN, BLACK, LTGRAY));

  pOkButton = &m_btnOK;

  int nButtonStyle;

  nButtonStyle = m_btnOK.GetButtonStyle();
  m_btnOK.SetButtonStyle(nButtonStyle | BS_OWNERDRAW, FALSE);

  oldWndProc = (WNDPROC) SetWindowLong(pWindowDlg->m_hWnd, GWL_WNDPROC,
    (DWORD) MyWndProc);

  return TRUE;
}

That does it for IDOK only. This was sort of neat!

Steve

PS: And "GTR" stand for guitar, my last name is Miller.


0
 

Expert Comment

by:joet1989
ID: 2581380
For SteveGTR:  Looks good.  I also got it to work with your comments.
Could you further explain what the SetWindowLong is doing.  It looks like it is replacing a function call.  Will it replace the "oldWndProc" for the whole program?  If so, is this something that you want to do?  

Finally, Just for the record...

The "nButtonStyle|BS_OWNERDRAW" was not needed.  I know I suggested it but it was not necessary.

This was very interesting...
0
 
LVL 30

Expert Comment

by:SteveGTR
ID: 2581471
As I understand it, the SetWindowLong() with the GWL_WNDPROC parameter replaces the windows procedure for the window and returns the old windows procedure. Doing this will allow your program to intercept the WM_DRAWITEM messages for IDOK and IDCANCEL buttons on that window only.

You should "or in" the existing button style for IDOK button, because it is set as the default button (BS_DEFPUSHBUTTON). Not doing this will change the functionality of the open dialog.
0
 
LVL 1

Author Comment

by:Mensana
ID: 2581485
After you offered the previous solution (the one with the old style dialog box), I started to study in the documentation the customization for an explorer-style Open dialog box. The solution seemed to be a hook procedure, which is, somehow, exactly your approach.
Thank you very much. The points are yours.
M.

PS: Steve Howe and Steve Hackett are both guitar players. Together, they recorded an album called “GTR”. That’ll explain my question.

For joet1989:
The SetWindowLong function is used to replace the window procedure. The new window procedure will filter the messages and will send the WM_DRAWITEM message to each button. I made a small modification to Steve’s function so that can work for both buttons:

LRESULT CALLBACK MyWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
   if( (nMsg == WM_DRAWITEM) && ( (wParam == IDOK) || (wParam == IDCANCEL) ) )
   {
      pOkButton->SendMessage(WM_DRAWITEM, wParam, lParam); //DrawItem((LPDRAWITEMSTRUCT) lParam);
      return 1L;
   }

   return CallWindowProc(oldWndProc, hWnd, nMsg, wParam, lParam);
}

M.
0
 

Expert Comment

by:joet1989
ID: 2581504
Thanks SteveGTR for the explanation.

Thanks Mensana for the good problem.
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

Introduction: Displaying information on the statusbar.   Continuing from the third article about sudoku.   Open the project in visual studio. Status bar – let’s display the timestamp there.  We need to get the timestamp from the document s…
If you use Adobe Reader X it is possible you can't open OLE PDF documents in the standard. The reason is the 'save box mode' in adobe reader X. Many people think the protected Mode of adobe reader x is only to stop the write access. But this fe…
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.
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…

707 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

13 Experts available now in Live!

Get 1:1 Help Now