I have a toolbar to a window in my application. I have read MSDN docs and can handle command and notification messages. My problem is with a dropdown menu attached to a button on the toolbar. The menu items open modal dialog boxes and wait for user to finalize settings changes. After the user clicks 'ok' or 'cancel' focus returns to the main window, however, the toolbar is still under the impression that the left mouse button is down so everytime i drag the mouse over the toolbar button that was clicked the button takes the 'button is checked' state and 'appears' to be pressed.
Is the problem related to the tracking of mouse events?
Here is notification message handling within window proc:
case WM_NOTIFY:
{
LPNMHDR lpnm = ( ( LPNMHDR )lParam );
LPNMTOOLBAR lpnmTB = ( ( LPNMTOOLBAR )lParam );
switch( lpnm->code )
{
case TBN_DROPDOWN:
{
// Get the coordinates of the button.
RECT rc;
SendMessage( lpnmTB->hdr.hwndFrom, TB_GETRECT, ( WPARAM )lpnmTB->iItem, ( LPARAM )&rc );
// Convert to screen coordinates.
MapWindowPoints( lpnmTB->hdr.hwndFrom, HWND_DESKTOP, ( LPPOINT )&rc, 2 );
// handle dropdown menus
return HandleTexEditDropdown( hWnd, lpnmTB, rc );
}
default:
break;
}
break;
}
Here is handle TexEditDropdown():
LRESULT CALLBACK CWindowManager::HandleTexEditDropdown( HWND hWnd, LPNMTOOLBAR lpnm, RECT &rc )
{
HRESULT hr = S_OK;
switch( lpnm->iItem )
{
case IDM_EDITTEXTURE_FILL:
{
// Get the menu.
HMENU hMenuLoaded = LoadMenu( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDR_BUCKETFILL ) );
// Get the submenu for the first menu item.
HMENU hPopupMenu = GetSubMenu( hMenuLoaded, 0 );
// Set up the pop-up menu.
// In case the toolbar is too close to the bottom of the screen,
// set rcExclude equal to the button rectangle and the menu will appear above
// the button, and not below it.
TPMPARAMS tpm;
tpm.cbSize = sizeof( TPMPARAMS );
tpm.rcExclude = rc;
// Show the menu and wait for input.
// If the user selects an item, its WM_COMMAND is sent.
INT nCmd = TrackPopupMenuEx( hPopupMenu,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL | TPM_RETURNCMD,
rc.left, rc.bottom, hWnd, &tpm );
DestroyMenu( hMenuLoaded );
switch( nCmd )
{
case IDM_BUCKETFILLSETTINGS:
DialogBox( GetModuleHandle( NULL ),
MAKEINTRESOURCE( IDD_TEXEDITBUCKETFILL ),
hWnd,
( DLGPROC )TexEditSettingsProc );
break;
default:
return 0;
}
SendMessage( ( HWND )lpnm->hdr.hwndFrom, TB_MARKBUTTON, lpnm->iItem, MAKELPARAM( FALSE, 0 ) );
UpdateWindow( hWnd );
SetStateChange();
return 1;
}
case IDM_EDITTEXTURE_RECOVER:
{
// Get the menu.
HMENU hMenuLoaded = LoadMenu( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDR_RECOVER ) );
// Get the submenu for the first menu item.
HMENU hPopupMenu = GetSubMenu( hMenuLoaded, 0 );
// Set up the pop-up menu.
// In case the toolbar is too close to the bottom of the screen,
// set rcExclude equal to the button rectangle and the menu will appear above
// the button, and not below it.
TPMPARAMS tpm;
tpm.cbSize = sizeof( TPMPARAMS );
tpm.rcExclude = rc;
// Show the menu and wait for input.
// If the user selects an item, its WM_COMMAND is sent.
INT nCmd = TrackPopupMenuEx( hPopupMenu,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL | TPM_RETURNCMD,
rc.left, rc.bottom, hWnd, &tpm );
DestroyMenu( hMenuLoaded );
switch( nCmd )
{
case IDM_RECOVERFILE:
{
WCHAR wcs[ MAX_PATH ] = L"",
wcsFiletype[ MAX_PATH ] = L"",
wcsFilename[ MAX_PATH ] = L"";
D3DXIMAGE_INFO info;
// get currently loaded image info
if( FAILED( hr = CTextureEditor::GetImageInfo( &info ) ) )
{
DebugStringDX( ClassName, "Failed to CTextureEditor::GetImageInfo() at AuxiliaryViewportProcess()", __LINE__, hr );
break;
}
if( !CTextureEditor::CatImageFileType( info.ImageFileFormat, wcsFiletype ) )
{
DebugStringDX( ClassName, "Invalid image filetype at AuxiliaryViewportProcess()", __LINE__, hr );
break;
}
wsprintf( wcs, L"GDEImage Filetype (*%s)", wcsFiletype );
memcpy( &wcs[ 26 ], L"*", sizeof( WCHAR ) );
memcpy( &wcs[ 27 ], wcsFiletype, 4 * sizeof( WCHAR ) );
// Declare and initialize an OPENFILENAME struct to use for OpenFile Dialog
OPENFILENAME ofn;
ZeroMemory( ( void* )&ofn, sizeof( OPENFILENAME ) );
ofn.lStructSize = sizeof( ofn ); // SEE NOTE BELOW
ofn.hwndOwner = hWnd;
ofn.lpstrTitle = L"Recover Image From File";
ofn.lpstrFilter = wcs;
ofn.lpstrFile = wcsFilename;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
ofn.lpstrDefExt = L"image file";
ofn.lpstrInitialDir = app.GetBinDirectory();
// OpenFile Dialog
if( GetOpenFileName( &ofn ) )
{
if( FAILED( hr = CTextureEditor::Recover( 0, wcsFilename ) ) )
{
DebugStringDX( ClassName, "Failed to CTextureEditor::Recover() at AuxiliaryViewportProc()", __LINE__, hr );
break;
}
}
break;
}
default:
return 0;
}
SendMessage( ( HWND )lpnm->hdr.hwndFrom, TB_MARKBUTTON, lpnm->iItem, MAKELPARAM( FALSE, 0 ) );
UpdateWindow( hWnd );
SetStateChange();
return 1;
}
default:
return 0;
}
return 0;
}
I think the returned value for TBN_DROPDOWN is not correct -- you are returning 1 which maps to TBDDRET_NODEFAULT meaning "The drop-down was not handled", you'll need to return TBDDRET_DEFAULT or TBDDRET_TREATPRESSED.
Related
I have a CPropertySheet with three tabs. I have a different CPropertyPage class for each tab. When my CPropertySheet is loaded with the debugger, the first page is always shown correctly. However, when I click on any of the other tabs, the CPropertyPage area becomes blank. Even if I click back on the first tab, the area is still empty. I am using Visual Studio, MFC, C++.
I am trying to find the proper way to handle the different tab clicks and have my tabs show correctly. This is the code to initialize my property sheet and it's pages:
BOOL CSLIMOptCplusplusApp::InitInstance()
{
CWinApp::InitInstance();
SQLHENV m_1;
EnvGetHandle(m_1);
Login lgn; //Creates a Login dialog for the user to enter credentials.
lgn.DoModal();
CImageSheet* imagedlg = new CImageSheet( "Admin Options" );
CImageDisplay* pageImageDisplay = new CImageDisplay;
CImageDimensions* pageImageDimensions = new CImageDimensions;
ListOption* pageListOption = new ListOption;
ASSERT( imagedlg );
ASSERT( pageImageDisplay );
ASSERT( pageImageDimensions );
ASSERT( pageListOption );
imagedlg->AddPage( pageListOption);
imagedlg->AddPage( pageImageDisplay );
imagedlg->AddPage( pageImageDimensions );
imagedlg->m_psh.dwFlags |= PSH_NOAPPLYNOW; //Removes the default Apply button
imagedlg->Create();
imagedlg->ShowWindow( SW_SHOW );
m_pMainWnd = imagedlg;
This is the code for my CPropertySheet class:
BOOL CImageSheet::OnInitDialog()
{
CWnd* pOKButton = GetDlgItem( IDOK );
ASSERT( pOKButton );
pOKButton->ShowWindow( SW_HIDE );
CWnd* pCANCELButton = GetDlgItem( IDCANCEL );
ASSERT( pCANCELButton );
pCANCELButton->ShowWindow( SW_HIDE );
// Set Flags for property sheet
m_bModeless = TRUE;
m_nFlags |= WF_CONTINUEMODAL;
BOOL bResult = CPropertySheet::OnInitDialog();
m_bModeless = FALSE;
m_nFlags &= ~WF_CONTINUEMODAL;
//Get button sizes and positions
CRect rect, tabrect;
GetDlgItem( IDOK )->GetWindowRect( rect );
GetTabControl()->GetWindowRect( tabrect );
ScreenToClient( rect );
ScreenToClient( tabrect );
UpdateData( FALSE );
My problem was that I was setting m_bModeless to false,
BOOL bResult = CPropertySheet::OnInitDialog();
m_bModeless = FALSE; //Change to TRUE to fix the problem.
m_nFlags &= ~WF_CONTINUEMODAL;
I have a bizarre issue here. I'm displaying a semi-transparent splash screen (a .png file) using layered windows. It works on some machines but not others. On the machines where it doesn't work, the GetLastError returns 317 (which isn't very helpful). Has anyone experienced this before? Here are my relevant functions. The incoming parameters for CreateAsPNG are WS_VISIBLE|WS_POPUP (dwStyle), 0 (dwExStyle), and the handle to a hidden tool window for the parent so no taskbar entry is created. I've verified that I can load an embedded PNG resource into CImage and that the size of the image is correct.
Thanks in advance for any help!
BOOL MySplashWnd::CreateAsPNG( DWORD dwStyle, DWORD dwExStyle, const CString& sTitle, HWND hWndParent )
{
ATL::CImage img;
CreateStreamOnResource( m_nBitmapID, img );
m_nWndWidth = img.GetWidth();
m_nWndHeight = img.GetHeight();
int nTop = 0;
int nLeft = 0;
GetTopLeft( nTop, nLeft );
dwExStyle |= WS_EX_LAYERED;
// Create the Splash Window
BOOL bRetVal = CWnd::CreateEx( dwExStyle, AfxRegisterWndClass( CS_CLASSDC ), sTitle,
dwStyle, nLeft, nTop, m_nWndWidth, m_nWndHeight, hWndParent, NULL );
//Couldn't create the window for some unknown reason...
X_ASSERT( bRetVal != FALSE );
if ( bRetVal )
{
HDC hScreenDC = ::GetDC( m_hWnd );
HDC hDC = ::CreateCompatibleDC( hScreenDC );
HBITMAP hBmp = ::CreateCompatibleBitmap( hScreenDC, m_nWndWidth, m_nWndHeight );
HBITMAP hBmpOld = ( HBITMAP ) ::SelectObject( hDC, hBmp );
img.Draw( hDC, 0, 0, m_nWndWidth, m_nWndHeight, 0, 0, m_nWndWidth, m_nWndHeight );
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
POINT ptPos = { nLeft, nTop };
SIZE sizeWnd = { m_nWndWidth, m_nWndHeight };
POINT ptSource = { 0, 0 };
if ( ::UpdateLayeredWindow( m_hWnd, hScreenDC, &ptPos, &sizeWnd, hDC, &ptSource, 0, &blend, ULW_ALPHA ) )
{
}
else
{
// The last error value is 317 on some Win7 machines.
TRACE( _T( "*** Last error: %d\n" ), ::GetLastError() );
}
::SelectObject( hDC, hBmpOld );
::DeleteObject( hBmp );
::DeleteDC( hDC );
::ReleaseDC( NULL, hScreenDC );
}
return bRetVal;
}
void MySplashWnd::CreateStreamOnResource( UINT nIDRes, ATL::CImage& img )
{
HINSTANCE hInstance = ::GetMUIResourceInstance();
if ( hInstance == NULL )
{
return;
}
HRSRC hResource = ::FindResource( hInstance, MAKEINTRESOURCE( nIDRes ), "PNG" );
if ( hResource == NULL )
{
return;
}
DWORD dwResourceSize = ::SizeofResource( hInstance, hResource );
if ( dwResourceSize == 0 )
{
return;
}
HGLOBAL hImage = ::LoadResource( hInstance, hResource );
if ( hImage == NULL )
{
return;
}
LPVOID pvImageResourceData = ::LockResource( hImage );
if ( pvImageResourceData == nullptr )
{
return;
}
HGLOBAL hImageData = ::GlobalAlloc( GMEM_MOVEABLE, dwResourceSize );
if ( hImageData == NULL )
{
return;
}
LPVOID pvImageBuffer = ::GlobalLock( hImageData );
if ( pvImageBuffer != nullptr )
{
::CopyMemory( pvImageBuffer, pvImageResourceData, dwResourceSize );
::GlobalUnlock( hImageData );
IStream* pStream = nullptr;
if ( SUCCEEDED( ::CreateStreamOnHGlobal( hImageData, TRUE, &pStream ) ) )
{
img.Load( pStream );
pStream->Release();
}
::GlobalUnlock( hImageData );
}
::GlobalFree( hImageData );
} // CTTSplashWnd::CreateStreamOnResource
UPDATE: I've found that even on the same machine, sometimes UpdateLayeredWindow succeeds and other time it fails (but always with code 317 if it does fail). Another piece of info is that this splash is being run on a separate UI thread. It always works on my machine though...
I'm having this same problem and I can't find any info either on it. Using SetWindowAttributes method instead works, but I want to use the SetLayeredWindow way. I'm coming to the point where I'm going to debug through the whole windows API to find out what is happening since msdn gives jack info about this error message. The only difference is that the OpenGL layered window demo example I saw which uses this method, uses CreateDIBSection instead of CreateCompatibleBitmap which seems to work on my PC.
I've had UpdateLayeredWindow() work perfectly on a Windows 7 x86_64 machine only for it to start failing when i've disabled desktop composition. It turned out that
UpdateLayeredWindow (window_handle, NULL,
&position, &size,
buffer_hdc, &buffer_offset,
0, &blend_options, ULW_ALPHA);
works, while
UpdateLayeredWindow (window_handle, NULL,
&position, NULL,
buffer_hdc, &buffer_offset,
0, &blend_options, ULW_ALPHA);
does not work and fails with error 317.
I've been trying to skip some of the arguments because i didn't need to change window size or contents, just to move it. With desktop composition disabled, this turned out to be impossible.
Not sure how this relates to your original problem, as you're also supplying the screen DC, while i don't.
I have created TreeView like this:
TreeView=CreateWindowEx(0, WC_TREEVIEW, TEXT("Tree View"), WS_VISIBLE | WS_CHILD, 0, 0, 200, 500, hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
Now I added one item to it like shown on this website.
It all okay, but after hours and hours of googling I still didn't found answer to these questions:
How to add subitems (nodes)?
How to add checkbox on each item (how to determine if specified checkbox is checked)?
EDIT #4:
In response to OPs request, I have added an example that removes checkbox from a parent node.
THE PROBLEM IS THAT CHECKBOX STILL APPEARS WHEN USER SELECTS A NODE AND PRESSES SPACEBAR.
This question solves that problem.
EDIT #3:
I have added the code that creates a node that is already checked.
It is the second child bode in the WM_CREATE handler.
END OF EDIT
case WM_CREATE:
{
// this is your treeview
TreeView = CreateWindowEx(0, WC_TREEVIEW,
TEXT("Tree View"), WS_VISIBLE | WS_CHILD,
0, 0, 200, 500,
hwnd, (HMENU)ID_TREE_VIEW, GetModuleHandle(NULL), NULL);
/************ enable checkboxes **************/
DWORD dwStyle = GetWindowLong( TreeView , GWL_STYLE);
dwStyle |= TVS_CHECKBOXES;
SetWindowLongPtr( TreeView , GWL_STYLE, dwStyle );
/************ add items and subitems **********/
// add root item
TVINSERTSTRUCT tvis = {0};
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is root item";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = TVI_ROOT;
HTREEITEM hRootItem = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// and here is an example of removing a checkbox
// from a specific item/subitem in case you ever need it
TVITEM tvi;
tvi.hItem = hRootItem ;
tvi.mask = TVIF_STATE;
tvi.stateMask = TVIS_STATEIMAGEMASK;
tvi.state = 0;
TreeView_SetItem( TreeView, &tvi );
// add firts subitem for the hTreeItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is first subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
HTREEITEM hTreeSubItem1 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// now we insert second subitem for hRootItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT | TVIF_STATE; // added extra flag
tvis.item.pszText = L"This is second subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
// for demonstration purposes let us check this node;
// to do that add the following code, and add the extra flag for
// mask member like above
tvis.item.stateMask = TVIS_STATEIMAGEMASK;
tvis.item.state = 2 << 12;
HTREEITEM hTreeSubItem2 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// let us expand the root node so we can see if checked state is really set
TreeView_Expand( TreeView, hRootItem, TVE_EXPAND );
}
return 0L;
EDIT #2:
Here Is the part that explains how to check if item is checked ( it now properly checks when you click on a checkbox and when you press spacebar! ) :
case WM_NOTIFY:
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
if( lpnmh->idFrom == ID_TREE_VIEW ) // if this is our treeview control
{
switch( lpnmh->code ) // let us filter notifications
{
case TVN_KEYDOWN: // tree has keyboard focus and user pressed a key
{
LPNMTVKEYDOWN ptvkd = (LPNMTVKEYDOWN)lParam;
if( ptvkd->wVKey == VK_SPACE ) // if user pressed spacebar
{
// get the currently selected item
HTREEITEM ht = TreeView_GetSelection( ptvkd->hdr.hwndFrom );
// Prepare to test items state
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( ptvkd->hdr.hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
return 0L; // see the documentation for TVN_KEYDOWN
case NM_CLICK: // user clicked on a tree
{
TVHITTESTINFO ht = {0};
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints( HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1 );
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if(TVHT_ONITEMSTATEICON & ht.flags)
{
// Prepare to receive the desired information.
TVITEM tvItem;
tvItem.mask = TVIF_HANDLE | TVIF_STATE;
tvItem.hItem = (HTREEITEM)ht.hItem;
tvItem.stateMask = TVIS_STATEIMAGEMASK;
// Request the information.
TreeView_GetItem( lpnmh->hwndFrom, &tvItem );
// Return zero if it's not checked, or nonzero otherwise.
if( (BOOL)(tvItem.state >> 12) - 1 )
MessageBox( hwnd, L"Not checked!", L"", MB_OK );
else
MessageBox( hwnd, L"Checked!", L"", MB_OK );
}
}
default:
break;
}
}
}
break;
The relevant idea for proper testing when spacebar is pressed is handling of TVN_KEYDOWN message.
We use this message to get NMTVKEYDOWN structure filled, which will give us virtual key code of the pressed button and the HWND of the treeview that sent the notification.
Now we use TreeView_GetItem() macro to get the currently selected node and we check its state the same way we did when we did hit testing.
My only problem is concerning this part from the documentation for TVN_KEYDOWN:
Return value
If the wVKey member of lParam is a character key code, the character
will be used as part of an incremental search. Return nonzero to
exclude the character from the incremental search, or zero to include
the character in the search. For all other keys, the return value is
ignored.
I just do not know what to do with the return result so I have put 0L.
Important note: If you need to return value from dialog box procedure use something like this:
SetWindowLongPtr( hwnd, DWLP_MSGRESULT, (LONG_PTR)1 );
return TRUE;
see the remarks for Return value in this documentation and use SetWindowLongPtr instead of SetWindowLong so you can support both x32 and x64 versions of Windows.
That would be all. Hopefully you have your problem solved. If you need further help leave a comment.
END OF EDIT
I have never done checking if tree item is checked but I believe that accepted answer to this question is the way to go.
NOTE:
I would highly appreciate if there someone who can provide code snippet for showing how to determine if treeview node is checked or not.
My application has a message-only window that is launched from a newly created thread. The thread function creates the message-only window and runs the message pump. The problem I am having is that the message pump never seems to get the WM_CLOSE message. I've removed error-handling to simplify the posted code. Does anyone know what I'm doing wrong?
Constructor:
this->m_hInstance = ::GetModuleHandle( NULL );
this->m_wcx.cbSize = sizeof(WNDCLASSEX); // size of structure
this->m_wcx.style = CS_HREDRAW | CS_VREDRAW; // initially minimized
this->m_wcx.lpfnWndProc = &WndProc; // points to window procedure
this->m_wcx.cbClsExtra = 0; // no extra class memory
this->m_wcx.cbWndExtra = 0; // no extra window memory
this->m_wcx.hInstance = m_hInstance; // handle to instance
this->m_wcx.hIcon = ::LoadIcon( NULL, IDI_APPLICATION ); // default app icon
this->m_wcx.hCursor = ::LoadCursor( NULL, IDC_ARROW ); // standard arrow cursor
this->m_wcx.hbrBackground = NULL; // no background to paint
this->m_wcx.lpszMenuName = NULL; // no menu resource
this->m_wcx.lpszClassName = s_pwcWindowClass; // name of window class
this->m_wcx.hIconSm = NULL; // search system resources for sm icon
this->m_atom = ::RegisterClassEx( &m_wcx );
this->m_hNotifyWindowThread = ::CreateThread(
NULL, // no security attributes
0, // use default initial stack size
reinterpret_cast<LPTHREAD_START_ROUTINE>(NotifyWindowThreadFn), // function to execute in new thread
NULL, // thread parameters
0, // use default creation settings
NULL // thread ID is not needed
);
Destructor:
::DestroyWindow( this->m_hWnd );
::WaitForSingleObject( this->m_hNotifyWindowThread, NW_DEFAULT_TIMEOUT ); // <-- Seems to get stuck here.
::UnregisterClass( s_pwcWindowClass, this->m_hInstance );
Thread function:
s_ptInterface->pobjNotifyWindow->m_hWnd = ::CreateWindow(
s_pwcWindowClass, // window class name
s_pwcWindowName, // window name
WS_ICONIC, // window style is minimized
0, // initial horizontal position
0, // initial vertical position
CW_USEDEFAULT, // window width
0, // window height
NULL, // no parent window
NULL, // no menu
s_ptInterface->pobjNotifyWindow->GetInstanceHandle(), // associated instance
NULL // no additional info for WM_CREATE
);
::ShowWindow( s_ptInterface->pobjNotifyWindow->GetWindowHandle(), SW_HIDE );
::UpdateWindow( s_ptInterface->pobjNotifyWindow->GetWindowHandle();
dbt.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
dbt.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbt.dbcc_classguid = s_guidForCP210xDevices;
m_hNotify = RegisterDeviceNotification( m_hWnd, &dbt, DEVICE_NOTIFY_WINDOW_HANDLE );
while ( (blRetVal = ::GetMessage(
&msg, // message structure
NULL, // retrieve messages for all windows on this thread
0, // lowest message value to retrieve
0 // highest message value to retrieve
)) != 0 )
{
if ( blRetVal == -1 )
{
return ::GetLastError();
}
else
{
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
}
Window procedure:
switch ( uMsg )
{
case WM_CLOSE:
if ( m_hNotify != NULL )
{
::UnregisterDeviceNotification( m_hNotify );
m_hNotify = NULL;
}
::DestroyWindow( hWnd );
break;
case WM_DESTROY:
::PostQuitMessage( 0 );
break;
case WM_DEVICECHANGE:
if ( pHeader != NULL )
{
if ( pHeader->dbch_devicetype == DBT_DEVTYP_PORT )
{
switch ( wParam)
{
case DBT_DEVICEREMOVECOMPLETE: // Device is gone
::EnterCriticalSection( &(s_ptInterface->csSerialPort) );
s_ptInterface->pobjSerialPort->Close();
::LeaveCriticalSection( &(s_ptInterface->csSerialPort) );
break;
case DBT_DEVICEARRIVAL: // System detected device
::EnterCriticalSection( &(s_ptInterface->csSerialPort) );
s_ptInterface->pobjSerialPort->Open();
::LeaveCriticalSection( &(s_ptInterface->csSerialPort) );
break;
default:
// Do nothing.
break;
}
}
}
break;
default:
// Do nothing.
break;
}
return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
WM_CLOSE is sent when Windows asks your app to close the window. When the user clicks the upper right Close button or presses Alt+F4 for example. None of that is going to happen, you called DestroyWindow(). You need to use WM_DESTROY instead. Which is fine, no need to veto the close request.
I have really tough question.
I have a program with six different kinds of MDI Windows.
There needs to be a dialog prompt to prompt the graphic dimensions (Height, Width) and then create six different kinds of MDI Window depending on what kind of graphics.
Question is:
I already got 6 MDI types working (to some extent).
What do I do to create a customized File|New to accommodate the need for a custom dialog screen?
For example, in MDI, there's a generic File|New dialog box. I wish to customize that in MFC.
First of all you'll need to override the CDocument function that handles opening of new files; it's probably one of CDocument::OnNewDocument or CDocument::OnOpenDocument. I've also done this in the past by adding the following message map to CMainFrame, not sure which one the correct method is:
ON_COMMAND( ID_FILE_OPEN, &CMainFrame::OnFileOpen )
Then, to display a customized New File dialog, use the WINAPI GetOpenFileName function. Note that you must use this function, MFC's CFileDialog will not work. Here's a code snippet from the CMainFrame::OnFileOpen function I listed above in the message map:
/**
* Hook procedure for OPENFILENAME structure. This function processes messages from the custom
* controls added to the standard "Open File" dialog.
*
* #param [in] hdlg
* handle of the hdlg.
* #param [in] uiMsg
* message describing the user interface.
* #param [in] wParam
* the wParam field of the message.
* #param [in] lParam
* the lParam field of the message.
*
* #return If the return value is zero, the default dialog box procedure processes the message;
* otherwise it ignores the message.
*/
UINT_PTR CALLBACK OpenHexFileHook( HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam )
{
(void)(wParam);
if( !::IsWindow( hdlg ) ) {
return 0;
}
switch( uiMsg ) {
case WM_NOTIFY:
/* Sent when an event occurs or the custom controls require information */
{
LPOFNOTIFY pOfnNotify = (LPOFNOTIFY)lParam;
if( pOfnNotify == NULL ) {
return 0;
}
CWinApp *app = AfxGetApp();
switch( pOfnNotify->hdr.code ) {
case CDN_INITDONE:
/* Sent when the system has finished arranging the controls in the dialog box */
{
CString rv;
LRESULT idx;
/* Configure the Hex file format ComboBox */
HWND hHexFmt = GetDlgItem( hdlg, IDC_HEX_FILE_FORMAT );
ASSERT( hHexFmt );
/* Clear existing content */
::SendMessage( hHexFmt, CB_RESETCONTENT, 0, 0 );
/* Add new items to the list */
::SendMessage( hHexFmt, CB_ADDSTRING, 0, (LPARAM)_T("INTEL 32") );
::SendMessage( hHexFmt, CB_ADDSTRING, 0, (LPARAM)_T("INTEL 16") );
::SendMessage( hHexFmt, CB_ADDSTRING, 0, (LPARAM)_T("INTEL 8") );
/* Read user's last selection from registry */
rv = app->GetProfileString( _T("Settings"), _T("HexFormat"), _T("INTEL 32") );
idx = ::SendMessage( hHexFmt, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)((LPCTSTR)rv) );
if( idx == CB_ERR ) {
/* On error select the first item */
idx = 0;
}
/* Set current selection to the previously selected item's index */
::SendMessage( hHexFmt, CB_SETCURSEL, idx, 0 );
/* Configure the Bytes per address ComboBox */
HWND hBytePerLoc = GetDlgItem( hdlg, IDC_BYTES_PER_ADDR );
ASSERT( hBytePerLoc );
/* Clear existing content */
::SendMessage( hBytePerLoc, CB_RESETCONTENT, 0, 0 );
/* Add new items to the list */
::SendMessage( hBytePerLoc, CB_ADDSTRING, 0, (LPARAM)_T("1") );
::SendMessage( hBytePerLoc, CB_ADDSTRING, 0, (LPARAM)_T("2") );
::SendMessage( hBytePerLoc, CB_ADDSTRING, 0, (LPARAM)_T("4") );
/* Read user's last selection from registry */
rv = app->GetProfileString( _T("Settings"), _T("BytesPerAddr"), _T("1") );
idx = ::SendMessage( hBytePerLoc, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)((LPCTSTR)rv) );
if( idx == CB_ERR ) {
/* On error select the first item */
idx = 0;
}
/* Set current selection to the previously selected item's index */
::SendMessage( hBytePerLoc, CB_SETCURSEL, idx, 0 );
break;
}
case CDN_FILEOK:
/* Sent when the user specifies a file name and clicks the OK button */
{
/* Save user selection for the Hex file format ComboBox */
HWND hHexFmt = GetDlgItem( hdlg, IDC_HEX_FILE_FORMAT );
ASSERT( hHexFmt );
/* Get current selection's index */
LRESULT idx = ::SendMessage( hHexFmt, CB_GETCURSEL, 0, 0 );
if( idx != CB_ERR ) {
/* Get current selection's text length */
LRESULT len1 = ::SendMessage( hHexFmt, CB_GETLBTEXTLEN, idx, 0 );
if( len1 != CB_ERR ) {
TCHAR *text = new TCHAR[len1 + 1];
/* Get current selection's text */
LRESULT len2 = ::SendMessage( hHexFmt, CB_GETLBTEXT, idx, (LPARAM)text );
if( len1 == len2 ) {
/* Write string to registry */
app->WriteProfileString( _T("Settings"), _T("HexFormat"), text );
}
delete[] text;
}
}
/* Save user selection for the Bytes per address ComboBox */
HWND hBytePerLoc = GetDlgItem( hdlg, IDC_BYTES_PER_ADDR );
ASSERT( hBytePerLoc );
/* Get current selection's index */
idx = ::SendMessage( hBytePerLoc, CB_GETCURSEL, 0, 0 );
if( idx != CB_ERR ) {
/* Get current selection's text length */
LRESULT len1 = ::SendMessage( hBytePerLoc, CB_GETLBTEXTLEN, idx, 0 );
if( len1 != CB_ERR ) {
TCHAR *text = new TCHAR[len1 + 1];
/* Get current selection's text */
LRESULT len2 = ::SendMessage( hBytePerLoc, CB_GETLBTEXT, idx, (LPARAM)text );
if( len1 == len2 ) {
/* Write string to registry */
app->WriteProfileString( _T("Settings"), _T("BytesPerAddr"), text );
}
delete[] text;
}
}
break;
}
default:
/* Do nothing */
break;
}
break;
}
default:
/* Do nothing */
break;
}
return 0;
}
void CMainFrame::OnFileOpen()
{
std::basic_string<TCHAR> filterSpec;
/* Use *.hex filter */
filterSpec = _T("Hex Files (*.hex)");
filterSpec += (std::basic_string<TCHAR>::value_type)'\0';
filterSpec += _T("*.hex");
filterSpec += (std::basic_string<TCHAR>::value_type)'\0';
OPENFILENAME ofn = { sizeof(ofn) };
TCHAR filePath[MAX_PATH] = { 0 };
TCHAR fileTitle[MAX_PATH] = { 0 };
ofn.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
ofn.hInstance = AfxGetInstanceHandle();
ofn.lpstrFilter = filterSpec.c_str();
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 1;
ofn.lpstrFile = filePath;
ofn.nMaxFile = sizeof(filePath) / sizeof(TCHAR);
ofn.lpstrFileTitle = fileTitle;
ofn.nMaxFileTitle = sizeof(fileTitle) / sizeof(TCHAR);
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = _T("Select Intel HEX file");
ofn.Flags = OFN_ENABLETEMPLATE | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST |
OFN_EXPLORER | OFN_ENABLESIZING | OFN_DONTADDTORECENT |
OFN_HIDEREADONLY | OFN_ENABLEHOOK;
ofn.lpstrDefExt = _T("hex");
ofn.lCustData = NULL;
ofn.lpfnHook = OpenHexFileHook;
ofn.lpTemplateName = MAKEINTRESOURCE(IDD_HEX_FILE_OPEN);
ofn.FlagsEx = 0;
if( GetOpenFileName( &ofn ) != false ) {
AfxGetApp()->m_pDocManager->OpenDocumentFile( ofn.lpstrFile );
SetTitle( ofn.lpstrFileTitle );
}
}
In that project I added 2 ComboBoxes to the standard File Open dialog. These MSDN docs describe how to customize the standard dialogs. This is the resource file (.rc2) customization that I used:
//
// HexViewer.RC2 - resources Microsoft Visual C++ does not edit directly
//
#ifdef APSTUDIO_INVOKED
#error this file is not editable by Microsoft Visual C++
#endif //APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
// Add manually edited resources here...
/////////////////////////////////////////////////////////////////////////////
//
// File Open Dialog Template
//
IDD_HEX_FILE_OPEN DIALOGEX 0, 0, 370, 40
STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS
EXSTYLE WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
LTEXT "&HEX file format:", IDC_HEX_FILE_FORMAT_TEXT, 67, 2, 58, 8, SS_NOTIFY
COMBOBOX IDC_HEX_FILE_FORMAT, 130, 0, 164, 100,
CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "&Bytes per address:", IDC_BYTES_PER_ADDR_TEXT, 67, 20, 63, 8, SS_NOTIFY
COMBOBOX IDC_BYTES_PER_ADDR, 130, 18, 164, 100,
CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
END
/////////////////////////////////////////////////////////////////////////////
Note that this method of customization is recommended only if you're programming for Windows XP and older versions. From Vista onwards MS recommends using the Common Item Dialog API for customizing, but I've never used that.