Все добрый день.
Столкнулся со следующей проблеммой:
Есть куча диалогов в которых используется некий CustomControl написанный на чистом API вставленный в редакторе ресурсов как Custom Control с указанием класса, стиле и т.д...
Мне понадобилось вмето него вставить свой, но написанный на MFC. Можно ли его вставлять также в редакторе ресурсов,т.е без написание своего кода, сабклашенья и т.д.... и если можно, то как это сделать, как написать контрол, что б он мог всталятся из редактора?
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Все добрый день.
ATP>Столкнулся со следующей проблеммой:
ATP>Есть куча диалогов в которых используется некий CustomControl написанный на чистом API вставленный в редакторе ресурсов как Custom Control с указанием класса, стиле и т.д...
ATP>Мне понадобилось вмето него вставить свой, но написанный на MFC. Можно ли его вставлять также в редакторе ресурсов,т.е без написание своего кода, сабклашенья и т.д.... и если можно, то как это сделать, как написать контрол, что б он мог всталятся из редактора?
1. Надо зарегистрировать класс окна
BOOL CCustomCtrl::RegisterControlClass()
{
WNDCLASS wcls;
// check to see if class already registered
static const TCHAR szClass[] = _T("custom_control_name");
if (::GetClassInfo(AfxGetInstanceHandle(), szClass, &wcls))
{
// name already registered - ok if it was us
return (wcls.lpfnWndProc == (WNDPROC)CCustomCtrl::WndProcHook);
}
// set new values
wcls.lpfnWndProc = CCustomCtrl::WndProcHook;
wcls.hInstance = AfxGetInstanceHandle();
wcls.lpszClassName = szClass;
return (RegisterClass(&wcls) != 0);
}
2. Реализовать CCustomCtrl::WndProcHook
LRESULT CALLBACK
CCustomCtrl::WndProcHook(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// create new item and attach it
CCustomCtrl* pCtrl = new CCustomCtrl(TRUE);
pCtrl->Attach(hWnd);
// set up wndproc to AFX one, and call it
::SetWindowLongPtr(hWnd, GWL_WNDPROC, (LONG_PTR)AfxWndProc);
// then call it for this first message
return ::CallWindowProc(AfxWndProc, hWnd, msg, wParam, lParam);
}
Да, и описание класса
class CCustomCtrl : public CWnd
{
public:
CCustomCtrl(BOOL bAutoDelete = FALSE)
: m_bAutoDelete(bAutoDelete) { };
static BOOL RegisterControlClass();
protected:
virtual void PostNcDestroy() { if (m_bAutoDelete) delete this; }
static LRESULT CALLBACK EXPORT WndProcHook(HWND, UINT, WPARAM, LPARAM);
//{{AFX_MSG(CCustomCtrl)
//}}AFX_MSG
DECLARE_MESSAGE_MAP();
private:
BOOL m_bAutoDelete;
};
Здравствуйте, kmn, Вы писали:
kmn>Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>>Все добрый день.
ATP>>Столкнулся со следующей проблеммой:
ATP>>Есть куча диалогов в которых используется некий CustomControl написанный на чистом API вставленный в редакторе ресурсов как Custom Control с указанием класса, стиле и т.д...
ATP>>Мне понадобилось вмето него вставить свой, но написанный на MFC. Можно ли его вставлять также в редакторе ресурсов,т.е без написание своего кода, сабклашенья и т.д.... и если можно, то как это сделать, как написать контрол, что б он мог всталятся из редактора?
kmn>1. Надо зарегистрировать класс окна
kmn>kmn>BOOL CCustomCtrl::RegisterControlClass()
kmn>{
kmn> WNDCLASS wcls;
kmn> // check to see if class already registered
kmn> static const TCHAR szClass[] = _T("custom_control_name");
kmn> if (::GetClassInfo(AfxGetInstanceHandle(), szClass, &wcls))
kmn> {
kmn> // name already registered - ok if it was us
kmn> return (wcls.lpfnWndProc == (WNDPROC)CCustomCtrl::WndProcHook);
kmn> }
kmn> // set new values
kmn> wcls.lpfnWndProc = CCustomCtrl::WndProcHook;
kmn> wcls.hInstance = AfxGetInstanceHandle();
kmn> wcls.lpszClassName = szClass;
kmn> return (RegisterClass(&wcls) != 0);
kmn>}
kmn>
kmn>2. Реализовать CCustomCtrl::WndProcHook
kmn>kmn>LRESULT CALLBACK
kmn>CCustomCtrl::WndProcHook(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
kmn>{
kmn> // create new item and attach it
kmn> CCustomCtrl* pCtrl = new CCustomCtrl(TRUE);
kmn> pCtrl->Attach(hWnd);
kmn> // set up wndproc to AFX one, and call it
kmn> ::SetWindowLongPtr(hWnd, GWL_WNDPROC, (LONG_PTR)AfxWndProc);
kmn> // then call it for this first message
kmn> return ::CallWindowProc(AfxWndProc, hWnd, msg, wParam, lParam);
kmn>}
kmn>
kmn>Да, и описание класса
kmn>kmn>class CCustomCtrl : public CWnd
kmn>{
kmn>public:
kmn> CCustomCtrl(BOOL bAutoDelete = FALSE)
kmn> : m_bAutoDelete(bAutoDelete) { };
kmn> static BOOL RegisterControlClass();
kmn>protected:
kmn> virtual void PostNcDestroy() { if (m_bAutoDelete) delete this; }
kmn> static LRESULT CALLBACK EXPORT WndProcHook(HWND, UINT, WPARAM, LPARAM);
kmn> //{{AFX_MSG(CCustomCtrl)
kmn> //}}AFX_MSG
kmn> DECLARE_MESSAGE_MAP();
kmn>private:
kmn> BOOL m_bAutoDelete;
kmn>};
kmn>
Спасибо!! Вы это из исходников выкопали?
Здравствуйте, AcidTheProgrammer, Вы писали:
ATP>Спасибо!! Вы это из исходников выкопали?
Это из MSDN-кого примера (Samples\VC\MFC\general\ctrltest\)
а вот только что сочинил (для VC 7.x и, надеюсь, выше):
namespace CustomCtrlHlp
{
LRESULT CALLBACK WndProcHook(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// WM_NCDESTROY прийдет только в случае если по каким-то причинам не получилось
// найти или создать зарегистрированный экземпляр класса
if (msg == WM_NCDESTROY)
return ::DefWindowProc(hWnd, msg, wParam, lParam);
ASSERT (msg == WM_NCCREATE);
LPCREATESTRUCT lpCreateStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
ASSERT (lpCreateStruct != 0);
// create new item and attach it
CWnd * pWnd = static_cast<CWnd*>(CRuntimeClass::CreateObject(lpCreateStruct->lpszClass));
if (pWnd == NULL)
return 0; // жди WM_NCDESTROY
pWnd->Attach(hWnd);
// set up wndproc to AFX one, and call it
::SetWindowLongPtr(hWnd, GWL_WNDPROC, (LONG_PTR)AfxWndProc);
// then call it for this first message
return ::CallWindowProc(AfxWndProc, hWnd, msg, wParam, lParam);
}
BOOL Register(CRuntimeClass * pClass)
{
// Класс должен быть наследником CWnd
if (!pClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)))
{
return FALSE;
}
// класс надо уметь создавать при помощи CRuntimeClass
if (pClass->m_pfnCreateObject == NULL)
{
TRACE(traceAppMsg, 0,
_T("Error: Trying to register window class which is not ")
_T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"),
pClass->m_lpszClassName);
return FALSE;
}
// check to see if class already registered
WNDCLASS wcls = {0};
const TCHAR * szClass = pClass->m_lpszClassName;
if (::GetClassInfo(AfxGetInstanceHandle(), szClass, &wcls))
{
// name already registered - ok if it was us
return (wcls.lpfnWndProc == (WNDPROC)WndProcHook);
}
// set new values
wcls.lpfnWndProc = WndProcHook;
wcls.hInstance = AfxGetInstanceHandle();
wcls.lpszClassName = szClass;
return (RegisterClass(&wcls) != 0);
}
}
#define DECLARE_REGISTERED_WINDOW(class_name, base_class)\
class class_name : public base_class\
{\
DECLARE_SERIAL(class_name)\
private:\
class_name() {}\
virtual void PostNcDestroy() { delete this; }\
};\
IMPLEMENT_SERIAL(class_name, base_class, -1)\
Пример использования:
app.cpp
DECLARE_REGISTERED_WINDOW(CCustomCtrl, CWnd);
DECLARE_REGISTERED_WINDOW(CCustomCtrl2, CWnd);
// ...
DECLARE_REGISTERED_WINDOW(CCustomCtrlN, CWnd);
// CcustomCtrlsApp initialization
BOOL CcustomCtrlsApp::InitInstance()
{
// InitCommonControls() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
InitCommonControls();
CWinApp::InitInstance();
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need
// Change the registry key under which our settings are stored
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
VERIFY (CustomCtrlHlp::Register(RUNTIME_CLASS(CCustomCtrl)));
VERIFY (CustomCtrlHlp::Register(RUNTIME_CLASS(CCustomCtrl2)));
// ...
VERIFY (CustomCtrlHlp::Register(RUNTIME_CLASS(CCustomCtrlN)));
CcustomCtrlsDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}
Шаблон диалога:
IDD_CUSTOMCTRLS_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Testing custom control registration"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,263,7,50,16
PUSHBUTTON "Cancel",IDCANCEL,263,25,50,16
CTEXT "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,
8
CONTROL "Custom1",IDC_CUSTOM1,"CCustomCtrl",WS_BORDER |
WS_TABSTOP,7,7,169,139
END
Здравствуйте, kmn:
Да круто.... супер нетривиально!!!!! Вот бы сам никогда не догадался сделать такую фабрку класса
И почему Microsoft токой пример не разместила гденьть в нормальном месте. Неужели они думают что этот код мало кому пригодится....
Здравствуйте, kmn, Вы писали:
kmn>Это из MSDN-кого примера (Samples\VC\MFC\general\ctrltest\)
Это всё конечно замечательно до тех пор, пока кто-нибудь не захочет отсабклассить такой контрол, например ничтоже сумяшеся вызвать DDX_Control(). Это может произойти если добавить переменную контрола через визард.
А произойдёт вот что.
DDX_Control() вызовёт СWnd::SubclassWindow(). А та первым делом позовёт CWwn::Attach(). Вот тут-то произойдёт облом! CWnd::Attach() надеется что описатель окна не присоеденено ни к одному СWnd в карте окон MFC.
ASSERT(FromHandlePermanent(hWnd) == NULL);
Как-бы не так! Описатель окна уже присоеденён к окну, созданному "фабрикой класса" в WndProcHook()! В результате появляются отладочные окна и утечки памяти.
Резюме. Контрол, написанный подобным образом нельзя использовать "стандартным" образом, через DDX_Control(). Через GetDlgItem() пожалуйста!
Интересно, есть-ли решение означенной проблемы?