Subclassing descendant of VCL TWinControl - c++

Using pseudo funcs for subclassing:
CreateSpecialHandle(TWinControl *Control, const TCreateParams &Params, const AnsiString SubClass)
{
......;
set Control DefWndProc to SubClass.lpfnWndProc
set Control WindowHandle from CreateWindowEx
......;
subclass(TWinControl *Control);
}
subclass(TWinControl *Control)
{
......;
oldWProc = (void*)GetWindowLong(Control->Handle, GWL_WNDPROC);
oldDefWProc = (void*)(Control->DefWndProc);
oldWindowProc = Control->WindowProc;
MakeObjectInstance(newWProc) for SetWindowLong
MakeObjectInstance(newDefWProc) for Control->DefWndProc
Control->WindowProc = newWindowProc;
......;
}
Now, we have unexpected behavior of subclassed control.
WM_NCHITTEST result 0, etc...
For example when newWProc intercepts WM_NCHITTEST and sets Result to HTCLIENT
we have mouse response, but, is that not responding without setting msg.result to 1 for msg.msg WM_NCHITTEST consequence of my mistake and wrong subclassing, what else we need to handle manually?
newWProc make callback of oldWProc
newDefWProc make callback of oldDefWProc
newWindowProc calls oldWindowProc
Do we have to subclass parent control of subclassed control as well?
Also, sending WM_GETTEXT results with empty buffer.
Obviously, we are doing something wrong here. We need explanation,
Thank You all in advance
Update:
in TDCEdit:public TCustomEdit overriding CreateWindowHandle
void __fastcal CreateWindowHandle(const TCreateParams &Params)
{
CreateSpecialHandle(this,Params,TEXT("EDIT"));
}
void CreateSpecialHandle(TWinControl *Control,const TCreateParams &Params, AnsiString SubClass)
{
...
Control->WindowHandle = CreateWindowEx(...,"EDIT",....);
....
subclass(Control);
}
subclass(TWinControl* Control)
{
......;
oldWProc = (void*)GetWindowLong(Control->Handle, GWL_WNDPROC);
oldDefWProc = (void*)(Control->DefWndProc);
oldWindowProc = Control->WindowProc;
MakeObjectInstance(newWProc) for SetWindowLong
MakeObjectInstance(newDefWProc) for Control->DefWndProc
Control->WindowProc = newWindowProc;
......;
}
Now, when I use TDCEdit and intercept Message.Msg == WM_NCHITTEST
inside newWProc Message.Result is 0 and stay 0 through all message process chain.
Note that subclassing TCustomEdit is one among other controls we need to subclass
in project and we try to use same subclass(TWinControl*) function for all.
Here is part of newWProc with few more lines to focus on problem
void __fastcall TControlWrapper::newWProc(Messages::TMessage &Message)
{
if(Message.Msg == WM_NCHITTEST ) // TEST
if(Message.Result == 0)
Message.Result=1;//<- WHY I NEED TO DO THIS
if( Message.Msg == WM_DESTROY) {
HandleWMDestroy(Message);
return;
}
CallWindowProcW( (int(__stdcall*)())oldWProc,
Handle, Message.Msg, Message.WParam,
Message.LParam);
if(Message.Msg == WM_NCHITTEST )
if(Message.Result == 0)Message.Result=1;//<- OR THIS
}

This is a confusing question - it doesn't help that your code samples are not C++.
set Control DefWndProc to SubClass.lpfnWndProc
is not a line in a C++ function, for example. Can you show your actual code please?
I can make a guess at what you're trying to do: are you trying to subclass a window (perhaps a form?) so that it moves when the mouse is clicked on it? If so, you don't need to do any raw Windows API-style subclassing, the way you appear to be doing with GetWindowLong. In C++ Builder, the VCL is an object-oriented wrapper around the Windows API, and you can do this in one of two much cleaner ways:
Create a new WindowProc and set it; this is a property pointing to a new window procedure, and you simply call the old one too;
Create a descendant class of your TWinControl (if you're using a form, you already have one) and implement the virtual method WndProc.
An example of #1, in Delphi (but you should be easily able to convert it to C++) is in the Embarcadero documentation on subclassing WndProc.
An example of #2, the cleanest OO version, is here, and this actually shows how to do what you're trying to do, too: C++Builder: Create a TForm with BorderStyle bsNone that is nevertheless movable and resizable
Given what you appear to want to do, I would suggest going with #2.

Related

How to check if CWnd message map contains message id without handling message?

ParentWnd contains mfc control called modeOfOperation (drop down list). When modeOfOperation is Normal everything is normal. We added new modeOfOperation=Extreme. When modeOfOperation is Extreme I want to disable 90% of existing ParentWnd controls because they don't work in Extreme mode. I have existing codebase with hundreds of UI controls. I want to find one place in code to disable 90% of them without hurting rest of functionality.
I know that 90% of UI controls that I need to disable are in several child windows. One of them is m_childWindow1. I need to tell if given message is is handled by m_childWindow1,...,m_childWindowN.
So ParentWnd routes messages to childWindow. I want to override childWindow handler in case when given message is handled by childWindow. So I need function like bool CWnd::isMessageIdInMessageMap(int id).
BOOL ParentWnd::OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo )
{
if ( nCode == CN_UPDATE_COMMAND_UI )
{
CWnd *contents = m_childWindow1->getContents();
if( contents )
{
if( contents->OnCmdMsg( nID, nCode, pExtra, pHandlerInfo ) )
{
//I want to enter additional code here
//But I don't want to call contents->OnCmdMsg
return true;
}
}
}
}
...
}
Simply use the existing functions (OnCmdMsg).
Create your own CCmdUI object (overwrite the Enable... functions if required) pass is as pExtra argument to OnCmdMsg and you know after the call if the was a handler or not.
There are no side effects...

Effect of destroying OK, CANCEL and HELP windows of a porperty sheet

I wanted to use a CPropertySheet based application for a project and I did not want those default OK, Cancel, Help and Apply buttons that come with a CPropertySheet class. Therefore, I destroyed those windows on OnInitDialog. Here is the code for reference:
BOOLCProductUI::OnInitDialog()
{
CPropertySheet::OnInitDialog();
CRect rect;
CButton *pTempBtn;
CButton SaveChanges;
pTempBtn = reinterpret_cast<CButton *>(GetDlgItem(IDHELP));
if (NULL != pTempBtn)
{
pTempBtn->GetWindowRect(&rect);
pTempBtn->DestroyWindow();
}
pTempBtn = reinterpret_cast<CButton *>(GetDlgItem(IDOK));
if (NULL != pTempBtn)
{
pTempBtn->DestroyWindow();
}
pTempBtn = reinterpret_cast<CButton *>(GetDlgItem(IDCANCEL));
if (NULL != pTempBtn)
{
pTempBtn->DestroyWindow();
}
pTempBtn = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
if (NULL != pTempBtn)
{
ScreenToClient(&rect);
pTempBtn->MoveWindow(rect);
pTempBtn->SetWindowText(_T("Save Changes"));
}
UpdateData(FALSE);
return TRUE;
}
CProductUI is a class of CPropertySheet.
However, when I compile the program using VC++2008 in Debug mode, I get a Debug Assertion Failed error message at the line
"CPropertySheet::OnInitDialog();"
Can anyone please shed some light on why this is happening?
Per How to Hide the Apply Button in CPropertySheet. Destroying window is not a proper solution to hide default buttons of property sheet. I would suggest you to use "ShowWindow()". But as you already mentioned your showwindow() also creating problem which is not possible if your calls are correct. Let it be, if your ShowWindow() is not working in "OnInitDialog()" function then better move this function to "OnCreate()". And also if it is not working then please share your whole .H and .CPP file.
You should call ShowWindow (SW_HIDE); instead of DestroyWindow();
Also there is no need to cast CWnd* returned by GetDlgItem() to CButton*.
Please also comment out your CButton SaveChanges; declaration. You
don't need it.
You can also use built-in flags to do that:
CMyPropertyPage myPage;
myPage.m_psp.dwFlags &= ~PSP_HASHELP;
myPropertySheet.AddPage(&myPage);
myPropertySheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;
myPropertySheet.m_psh.dwFlags &= ~PSH_HASHELP;
IMPORTANT: In general please run your application in Debug mode to see where it ASSERTs.

Disabling dialog OK button MFC

How do I disable MFC dialog OK button?
This code:
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
causes exception "Access violation reading location..."
in line ASSERT(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL)); of function CWnd::EnableWindow(BOOL bEnable) in winnocc.cpp from mfc90d.dll
In this time focus is on another control.
What's can be wrong?
Thanks for help.
[EDITED]
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
}
else
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(TRUE);
}
}
I'm not sure why would wouldn't be able to do it. If I take a regular CDialog and I do an init like this:
BOOL CMyDialog::OnInitDialog() {
CDialog::OnInitDialog();
CWnd *okbtn = GetDlgItem( IDOK );
if ( okbtn ) {
okbtn->EnableWindow( FALSE );
}
return TRUE;
}
it disables the button just fine. Perhaps something else is wrong?
Try this: http://support.microsoft.com/kb/122489
How to Disable Default Pushbutton Handling for MFC Dialog
Although default button (pushbutton) support is recommended, you might
want to disable or modify the standard implementation in certain
situations. You can do this in an MFC application by following these
steps:
Load the dialog into App Studio and change the OK button identifier
from IDOK to something else such as IDC_MYOK. Also, clear the check
from Default Button property.
Use ClassWizard to create a message
handling function for this button named OnClickedMyOK. This function
will be executed when a BN_CLICKED message is received from this
button.
In the code for OnClickedMyOK, call the base class version of
the OnOK function. Here is an example:
void CMyDialog::OnClickedMyOK()
{
CDialog::OnOK();
}
Override OnOK for your dialog, and do nothing inside the function. Here is an example:
void CMyDialog::OnOK()
{
}
Run the program and bring up the dialog. Give focus to a control other
than the OK button. Press the RETURN key. Notice that CDialog::OnOK()
is never executed.
I suspect the problem comes from pDlg pointer. When you call pDlg->GetDlgItem(IDOK), is the dialog already created already?
Make a breakpoint at the line CWnd* fieldOK = pDlg->GetDlgItem(IDOK); and debug into it to see if fieldOK pointer is null or a valid pointer.
That is why I think mark's answer is very close. You can disable it onOnInitDialog` or other members of you dialog class after it showed up.
The problem you have is that the button control has not been created on the interface yet. We do not get the full vision of your problem.
Anyway, you should protect your code from crashing. It is better that your code does nothing than to crash the application. Restructuring it like this avoids the access violation problem due to the NULL pointer:
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
if (fieldOK)
{
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
fieldOK->EnableWindow(FALSE);
else
fieldOK->EnableWindow(TRUE);
}
}
You need to load a bitmap for the disable mode of the OK button in LoadBitmaps() function.

C++ - WINAPI - Object-oriented approach to closing a window

While trying to create a nice wrapper around Win32 specific GUI components, I eventually ran into a problem. The problem is that I'm unable to close the application after the windows I created no longer exist.
My API works like this:
/// ----------------------------
/// #author God
/// #project Helixirr Widgets
/// ----------------------------
#include <helixirrwidgets/HelixirrWidgets.hpp>
int main(void){
HelixirrWidgets::Window __windows[2] = {HelixirrWidgets::Window("Big Window"), HelixirrWidgets::Window()};
__windows[0].position(200, 200);
__windows[0].size(800, 600);
__windows[0].visible(true);
__windows[0].save_changes();
__windows[1].name("Tiny Window");
__windows[1].position(10, 100);
__windows[1].size(400, 200);
__windows[1].visible(true);
__windows[1].save_changes();
while(__windows[0].active() || __windows[1].active()){
if(__windows[0].visible()){
__windows[0].show();
}
if(__windows[1].visible()){
__windows[1].show();
}
}
return 0;
}
In method of HelixirrWidgets::Window called "active", which is declared like this
inline bool active(void) const noexcept;
I can check, whether my window is active or not.
This method basically return a const reference to a boolean member variable of an instance. This member variable is modified in "show"-method of the same class. Here's the definition:
void Window::show(void){
if(GetMessage(&_m_opHelper->message, _m_opHelper->handle_window, 0, 0)){
if(_m_opHelper->message.message == WM_CLOSE){
_m_bActive = false;
return;
}
TranslateMessage(&_m_opHelper->message);
DispatchMessage(&_m_opHelper->message);
ShowWindow(_m_opHelper->handle_window, SW_SHOWDEFAULT);
UpdateWindow(_m_opHelper->handle_window);
_m_bActive = true;
return;
}
_m_bActive = false;
}
Do note I use pimpl-idiom to hide platform-specific structures ("_m_opHelper" is pointer to implementation).
It may look like it works, but it doesn't and I can't understand why. It all comes down to a simple question: how can I close my window implemented using WINAPI specific functions and structures to be closed appropriately by a user of my application?
I guess the cause of the issue is related to the fact WM_CLOSE simply is not last message HWND gets. Messages like WM_DESTROY, WM_NCDESTROY and possibly more (depending on the particlar window and its state) will come after WM_CLOSE, leading to the assignment _m_bActive = TRUE.
I.e. the window becomes inactive for very short time, and (likely) they will never be inactive at the same time, causing an endless loop in main().

How can I create an ITaskbarList3 in C++ Builder?

I'm trying to use the ITaskbarList3 interface introduced with Windows 7 so that I can show task progress for a lengthy task in my taskbar icon. The documentation states that I should wait for a TaskbarButtonCreated message before trying to initialize my ITaskbarList3 component, but I don't seem to be getting any TaskbarButtonCreated messages.
Here is what I have so far:
I have a global variable in my .cpp file to store the custom message ID for TaskbarButtonCreated.
static const UINT m_uTaskbarBtnCreatedMsg =
RegisterWindowMessage( _T("TaskbarButtonCreated") );
I created a separate WndProc function to handle the new message.
void __fastcall TForm1::WndProcExt(TMessage &Message)
{
if(Message.Msg == uTaskbarBtnCreatedMsg && uTaskbarBtnCreatedMsg != 0) {
OnTaskbarBtnCreated();
}
else {
WndProc(Message);
}
}
In my form constructor, the very first line sets the WindowProc property to WndProcExt to route messages. I also tried tossing in a ChangeWindowMessageFilter to see if the TaskbarButtonCreated message was being filtered for some reason.
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
WindowProc = WndProcExt;
ChangeWindowMessageFilterEx(Handle, uTaskbarBtnCreatedMsg, MSGFLT_ALLOW, NULL);
...
}
In the debugger, the return value from ChangeWindowMessageFilterEx is always true. I've also confirmed my WndProcExt function receives all kinds of Windows messages, just not the one I'm looking for. The OnTaskbarBtnCreated function never gets called.
Am I missing a step? Is the message being filtered out or sent before my message handler is ready for it?
It is not a good idea to have the TForm assign a value to its own WindowProc property. For starters, the Handle window may have already been allocated before your constructor is even entered, due to DFM streaming, so you would miss all of the window's initial messages (which there can be several) before your constructor starts running. You need to override the virtual WndProc() method instead, and do pass the TaskbarButtonCreated message to the default handler, don't block it:
static const UINT m_uTaskbarBtnCreatedMsg = RegisterWindowMessage( _T("TaskbarButtonCreated") );
void __fastcall TForm1::WndProc(TMessage &Message)
{
TForm::WndProc(Message);
if ((Message.Msg == uTaskbarBtnCreatedMsg) && (uTaskbarBtnCreatedMsg != 0))
OnTaskbarBtnCreated();
}
As for ChangeWindowMessageFilterEx(), you need to call that every time the TForm's Handle window gets (re)allocated (which can happen multiple times during the Form's lifetime), so you need to override the virtual CreateWnd() method instead:
void __fastcall TForm1::CreateWnd()
{
TForm::CreateWnd();
if (CheckWin32Version(6, 1) && (uTaskbarBtnCreatedMsg != 0))
ChangeWindowMessageFilterEx(Handle, uTaskbarBtnCreatedMsg, MSGFLT_ALLOW, NULL);
// any other Handle-specific registrations, etc...
}
void __fastcall TForm1::DestroyWindowHandle()
{
// any Handle-specific de-registrations, etc...
TForm::DestroyWindowHandle();
}
Lastly, set the TApplication::ShowMainFormOnTaskbar property to true in the project's WinMain() function before your MainForm is created so its window, rather than the TApplication window, is managing the taskbar button (and to enable other Vista+ related features, like Flip 3D and Taskbar previews). Otherwise, you will have to use the TApplication::HookMainWindow() method to intercept any "TaskbarButtonCreated" messages that may get sent to the TApplication window.