|
Need your thoughts and/or opinion. Question at the bottom.
When user closes the modeless dialog box you can trap the WM_CLOSE message to insure proper cleanup.
According to Help
CWnd::OnClose
The framework calls this member function as a signal that the CWnd or an application is to terminate. The default implementation calls DestroyWindow.
I found two ways to approach the user who closes the modeless dialog box by clicking the x on the upper right hand corner of the window
Looking at the first example below.
I found out that if you don't call DestroyWindow() from your OnClose() and you don't want multiple instances of your modeless opened you will need to set the pointer to NULL here (otherwise your pointer value can't be used as a flag to prohibit other instances of your modeless dialog from being opened). PostNcDestroy apparently isn't run until the parent is destroyed. Once the parent/owner is closed then a PostNcDestroy is run for each modeless that opened - one right after another.
void CDialogDerived::OnClose()
{
CDialog::OnClose();
m_pParent->m_pModelessDialog = NULL;
}
void CDialogDerived::PostNcDestroy()
{
CDialog::PostNcDestroy();
m_pParent->m_pModelessDialog = NULL; //in case destroy window is called from your code and OnClose is never run
delete this;
}
Example 2. This seems like a cleaner approach as the Dialog is destroyed immediately and thus your pointer is set to
NULL immediately
void CDialogDerived::OnClose()
{
CDialog::OnClose();
DestroyWindow();
}
void CDialogDerived::PostNcDestroy()
{
CDialog::PostNcDestroy();
m_pParent->m_pModelessDialog = NULL;
delete this;
}
Any thoughts pro and con to either of these approaches? I'm curious why the PostNcDestroy is not run promptly
with the OnClose call on the first example.
Thanks!
|
|
|
|
|
Why make this so complex? You can easily check if the window still exists from the parent with a check like this:
if ( NULL == m_pModelessDialog->GetSafeHwnd() )
{
if ( NULL != m_pModelessDialog )
delete m_pModelessDialog;
} And just forget about all the messy cleanup in the dialog itself.
|
|
|
|
|
mx483 wrote: you don't want multiple instances of your modeless opened you will need to set the pointer to NULL
This is a curious way to avoid multiple instance. Is this a child dialog of your app or, the app itself ? Why don't you check if the dialog already exists before starting a new one ? If you destroy your dialog, a simple check on SafeWindow will tell you that your pointer is not used anymore. YOu could even fire up a message to your parent to reset the pointer if you really want to.
As for the examples, I think this is sufficient, no need to intercept another message.
void CDialogDerived::PostNcDestroy()
{
m_pParent->m_pModelessDialog = NULL;
delete this;
CDialog::PostNcDestroy();
}
~RaGE();
[edit] I wrote teh same as Shog because I forgot to post my message right away... Nevermind, and great to see we are on the same wave length [/edit]
-- modified at 10:01 Wednesday 15th February, 2006
|
|
|
|
|
Thanks for the responses. I do check for the NULL pointer before opening an instance. I didn't include that code because it isn't relevant. What is relevant though is the correct way to handle the WM_CLOSE message.
And my curiosity was I didn't understand why the WM_CLOSE message didn't run the Destroy Window right away - only after parent was closed.
|
|
|
|
|
I think you both missed my message. You have to intercept the WM_CLOSE message. If you don't you are
1. Not Destroying your window till your parent is closed.
2. Therefore you PostNcDestroy is not run until your parent is closed
3. Therefore, how does your app know that the pointer is no longer valid.
You can run GetSafeWnd all day long - it still points to valid memory...cause you never bothered to delete the CWnd object...until the parent is closed.
So back to my original question please.
|
|
|
|
|
mx483 wrote: 1. Not Destroying your window till your parent is closed.
Sorry, i guess i missed that. Sounds like you're missing a few things in your implementation, so i'll go over the steps to doing a proper modeless dialog in MFC:
- Create with
Create() , followed by ShowWindow() , not DoModal() (i'm sure you're already doing this ) - Never call
EndModalLoop() (or EndDialog() ) - Never let the default behavior run when it will end up doing what you avoided in #1 or #2
- No default behavior for
OnOK() or OnCancel() (WM_COMMAND handlers for IDOK and IDCANCEL ) - Be aware that the default behavior for
WM_CLOSE just posts a WM_COMMAND for IDCANCEL (so it'll end up calling IDCANCEL ) - Be aware that various keys can trigger
OnCancel() /OnOK() directly (ENTER, ESC) or indirectly (Alt+F4 -> WM_CLOSE -> OnCancel() ). So you really do need to override these commands, even if you don't have an explicit IDOK or IDCANCEL button on your dialog.
So the likely explanation for the behavior you describe is that you're letting the baseclass call EndModalLoop() , which just hides the window. Then when the parent is destroyed, its (now hidden) children are also destroyed, causing the wave of delayed destruction you noticed.
So your fix is simple: override OnOK() and OnCancel() and cause them to call DestroyWindow() rather than the baseclass implementation. Optionally, do the same for OnClose() (isn't necessary, but will make it more obvious what you're doing).
And i'd still encourage you to avoid messing with data in the parent window class (and especially doing delete this ). Nothing absolutely wrong with it, but it's messy - let the owner handle that stuff.
|
|
|
|
|
Bingo!
Hey thanks, Here was where I wasn't understanding:
Be aware that the default behavior for WM_CLOSE just posts a WM_COMMAND for IDCANCEL (so it'll end up calling IDCANCEL).
Thanks for taking the time to write again and explaining why the window wasn't really destroyed when the WM_CLOSE was called.
I'll also strongly consider your other suggestions.
|
|
|
|
|
I believe that fact that the Dialog was not getting destroyed was because the WM_CLOSE message is being overridden by CDialog and calling and IDCANCEL. That would make sense. Before I posted the question though I attempted to follow the WM_CLOSE message to see where it was going - hence the reference to CWnd::Close from Help at the top of the question. When I put a break point at the OnClose function -user clicks the x at upper right hand part of dialog(Created through ClassWizard capturing the WM_CLOSE message) I am lead to this.
_AFXWIN_INLINE void CWnd::OnClose()
{ Default(); }
Continuing I am lead to
LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)
Message being passed is 16 and IDCANCEL is defined as 2
Now I'm scratching my head. Wouldn't I see an IDCANCEL call or something like that. CWnd is supposed to destroy the window
Thanks
|
|
|
|
|
(btw - no harm in starting a new thread with offshoot topics like this )
This question takes you into the fun and exciting world of Classic Win32. Well, perhaps i'm being generous with "fun and exciting"... but anyway...
In a nutshell, this is how it works: in basic ol' Win32 (and Win16 for that matter), dialogs are not quite the same as "regular" windows. They aren't created in the same way, they don't get passed messages in the same way, and the default behavior for unprocessed messages isn't the same either. MFC does a lot to hide this from you, but at its core it still behaves in much the same way. Which makes things interesting when you're creating a non-modal by deriving a class from CDialog , because you inherit a lot of defaults that you really don't want.
And one of those behaviors is what happens when you call ::DefWindowProc() with WM_CLOSE as the message. As you've found, dialogs do not call DestroyWindow() - they post WM_COMMAND with WPARAM set to 2 (IDCANCEL ). And because the message is posted, you don't get a nice call stack leading back to OnClose() from OnCancel() .
Why is this so? Because you don't close dialogs - real modal dialogs - by calling DestroyWindow() . They have their own message loops that need to be cleaned up, and those take care of actually destroying the window, so you call EndModalLoop() in MFC, or EndDialog() in Win32. And that is what the default behavior for OnClose() /OnOk() end up doing... which as you've noticed, is nearly useless for non-modal dialogs.
|
|
|
|
|
Yeah, its starting to make sense. But why when I hovered over the nMsg in the DefWindowProc did the message come back as 16. I know IDCANCEL is 2
#define IDOK 1
#define IDCANCEL 2
#define IDABORT 3
#define IDRETRY 4
#define IDIGNORE 5
#define IDYES 6
#define IDNO 7
#if(WINVER >= 0x0400)
#define IDCLOSE 8
#define IDHELP 9
Still scratching. Oh and by the way thanks for your help.
|
|
|
|
|
Here's a little trick: in the debugger, put a number (or variable) in the watch window, and follow the name with , wm - it'll display the name of the window message that the number maps to. But you're not gonna see IDCANCEL being passed in nMsg anyway - it's passed in the wparam field, while the message is WM_COMMAND .
|
|
|
|
|
Sounds neat, I wish I could get it to work. Both wparam and lparam are zero with nMsg = 16. I using Visual C++ 6.0 so maybe the watch stuff is a new edition.
IDCANCEL, wm gives me an error message in the watch window.
I also looked up the value of WM_COMMAND and it is 273
|
|
|
|
|
mx483 wrote: IDCANCEL, wm gives me an error message in the watch window.
Wrong way about. Try:
16, wm<br /> or
nMsg, wm<br /> Put a breakpoint in OnCancel() , and look up the callstack when it gets hit - you'll see WM_COMMAND being processed then.
|
|
|
|
|
Thanks for being so patient. Yep It works, it was case sensitive. I see where the WM_CLOSE message is being posted. I can keep pressing F11 and trace into the functions where finally the DefWindowProc shows up. Then I can go to the call stack and see all this too. I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message.
Hey thanks you are teaching me a bunch!
|
|
|
|
|
mx483 wrote: I think I'm back to wondering why (according to you) I should be seeing a WM_COMMAND message instead of a WM_CLOSE message.
You'll see both - WM_CLOSE first, then (if you let the default handler handle it) WM_COMMAND .
|
|
|
|
|
Thanks for your patience and thoughtful comments. Jay
|
|
|
|
|
I have a lattice, each cell of it stores a random number between 0 and another number (for example 25000). I would like to visualize this lattice on a Windows Form by coloring its cells with different shades of a color, according to the number they contain, I mean small number->light shade, large number->dark shade. Does anyone have a good idea how can I get those shades?
Thanks in advance.
|
|
|
|
|
by lattice, you mean something like a grid/2d array/spreasheet ?
I suggest you display your data using Chris Maunder's Grid Control[^], and define one new cell type that will draw itseld according to the number drawn inside it.
to define the shading, you have a number of choices, I suggest something simple using RGB values; to use shades of grey; but there is a resolution issue, depending on the range of the numbers in the cells ( between lower and upper values ) each grey shade will be used for a range of number.
int iRandomNumber; int iLowerBound = 0;
int iUpperBound = 25000;
int iShadeValue;
iShadeValue = ( iRandomNumber / iUpperBound ) * 255;
COLORREF cShadeColor = RGB( iShadeValue, iShadeValue, iShadeValue );
Maximilien Lincourt
Your Head A Splode - Strong Bad
|
|
|
|
|
Here's how to make a rainbowish colours:
COLORREF Rainbow(unsigned int val)
{
enum
{
max = 255,
h = max/2
};
BYTE red;
if ( val<h )
{
red = static_cast<BYTE>(255.0*(1.0-double(val)/double(h)) + 0.5);
}
else
{
red = 0;
}
BYTE green;
if ( val<h )
{
green = static_cast<BYTE>(255*(double(val)/double(h)) + 0.5);
}
else
{
green = static_cast<BYTE>(255*(2.0-double(val)/double(h)) + 0.5);
}
BYTE blue;
if ( val<=h )
{
blue = 0;
}
else
{
blue = static_cast<BYTE>(255*(double(val)/double(h)-1.0) + 0.5);
}
return RGB(red, green, blue);
}
0->Red, 128->Green, 255->Blue and the rest fall between.
Steve
|
|
|
|
|
Here's code to make a better rainbow:
-------------------------------------
COLORREF Rainbow(int x)
{
enum
{
maximum = 255, // Range of x is from 0 to this value.
h1 = maximum/4,
h2 = maximum/2,
h3 = 3*maximum/4,
};
const double m = 255/h1;
BYTE red;
if ( x<=h1 )
{
red = 255;
}
else if (x>=h2)
{
red = 0;
}
else
{
red = static_cast<BYTE>(-m*x+510.0 + 0.5);
}
BYTE green;
if (x<h1)
{
green = static_cast<BYTE>(m*x + 0.5);
}
else if (x>h3)
{
green = static_cast<BYTE>(-m*x+1020.0 + 0.5);
}
else
{
green = 255;
}
BYTE blue;
if (x<=h2)
{
blue = 0;
}
else if (x>=h3)
{
blue =255;
}
else
{
blue = static_cast<BYTE>(m*x-510.0 + 0.5);
}
return RGB(red, green, blue);
}
Steve
|
|
|
|
|
Hi,
Thanks for your help, I got really nice rainbowish colors. But I need to get only the shadows of one color (for example: different shades of grey or red). I have tried to modify your code, unfortunatelly I could't get the right solution. Is the method proposed by you suitable to solve this problem? Thanks. DirA
|
|
|
|
|
Code like this should do it:
double f = double(val)/max_val;
COLORREF Shade = RGB(GetRValue(BaseColour)*f, GetGValue(BaseColour)*f, GetBValue(BaseColour)*f);
Where
- val determines the shade an in between 0 and max_val .
- BaseColour is the base colour, the colour you want shades of.
Steve
|
|
|
|
|
How to create an application, which open multiple instances without invoking new exe.
For example if we open different word documents, many new word application will open, but there will be only one exe running at a time.
Thanks
anil
|
|
|
|
|
See here.
"The greatest good you can do for another is not just to share your riches but to reveal to him his own." - Benjamin Disraeli
|
|
|
|
|
It is for avoiding multiple instance. I need an application, whcih can spawn many instance from same exe. In WORD, if we open many word documents, we can see many windows as orginal one, but if we go to TaskManger, ther is only one WindWord.exe...
|
|
|
|