在调用 DDX_Control 之前,如何更改使用 DDX_Control 初始化的控件 (CListBox) 的样式
How can I change the style of a control (CListBox) initialized with DDX_Control, before DDX_Control is called
我正在修改一个现有的项目并且一个对话框有一些控件我在某些情况下将其子类化为不同的主题(在其他情况下我将完全不理会它)。在 DoDataExchange()
期间调用 DDX_Control()
时,ListBox
的 hwnd 已经应用了样式。具体来说,此时即使我做SetWindowLongPtr()
,LBS_OWNERDRAWFIXED
也不行。 "does not work," 我的意思是虽然应用了样式,但 CListBox 未收到所有者绘制消息。
相反,如果我避免使用 DDX_Control()
并简单地进行创建,ListBox 确实会收到消息并且可以是所有者绘制的。但是如果我这样做,现在有两个 HWND,GetDlgItem()
只返回其中一个。我相信如果有必要我可以完成这项工作,但我想知道是否有一个秘密可以拦截对话框中控件的 HWND 创建(实际上是一个 CPropertyPage)。
下面是不起作用的代码,如果可能的话,有更多注释的代码 "works" 但不是我希望它工作的方式。
void CMyPropertySheet::DoDataExchange(CDataExchange* pDX)
{
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(IDC_LIST1, &hWndCtrl);
if (themed) {
DWORD style = GetWindowLongPtr(hWndCtrl, GWL_STYLE) | LBS_OWNERDRAWFIXED;
SetWindowLongPtr(hWndCtrl, GWL_STYLE, style);
DDX_Control(pDX, IDC_LIST1, m_listbox);
//RECT wr;
//::GetWindowRect(hWndCtrl, &wr);
//m_listbox.Create(style, wr, this, IDC_LIST1);
} else {
DDX_Control(pDX, IDC_LIST1, m_listbox);
}
我可能应该补充一下,我尝试对 window 进行子类化,但没有帮助,而且 CMyPropertySheet::PreSubclassWindow
也不够快。
好的,我不确定我是否向任何人推荐这个,但我终于找到了修改模板的方法。我不得不使用 VirtualProtect 来解锁模板的内存。
for (int i = 0; i < m_pages.GetSize(); i++) {
CPropertyPage* pPage = GetPage(i);
PROPSHEETPAGE* tpsp = &pPage->m_psp;
const DLGTEMPLATE* pTemplate;
if (tpsp->dwFlags & PSP_DLGINDIRECT) {
pTemplate = tpsp->pResource;
} else {
HRSRC hResource = ::FindResource(tpsp->hInstance, tpsp->pszTemplate, RT_DIALOG);
if (hResource == NULL) return false;
HGLOBAL hTemplate = LoadResource(tpsp->hInstance, hResource);
if (hTemplate == NULL) return false;
pTemplate = (LPCDLGTEMPLATE)LockResource(hTemplate);
if (pTemplate == NULL) return false;
}
if (afxOccManager != NULL) {
DLGITEMTEMPLATE *pItem = _AfxFindFirstDlgItem(pTemplate);
DLGITEMTEMPLATE *pNextItem;
BOOL bDialogEx = IsDialogEx(pTemplate);
int iItem, iItems = DlgTemplateItemCount(pTemplate);
for (iItem = 0; iItem < iItems; iItem++) {
pNextItem = _AfxFindNextDlgItem(pItem, bDialogEx);
DWORD dwOldProtect, tp;
if (bDialogEx) {
_DialogSplitHelper::DLGITEMTEMPLATEEX *pItemEx = (_DialogSplitHelper::DLGITEMTEMPLATEEX *)pItem;
if (pItemEx->id == IDC_LIST1) {
if (VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), PAGE_READWRITE, &dwOldProtect)) {
pItemEx->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), dwOldProtect, &tp);
}
}
} else {
if (pItem->id == IDC_LIST1) {
if (VirtualProtect(&pItem->style, sizeof(pItem->style), PAGE_READWRITE, &dwOldProtect)) {
pItem->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItem->style, sizeof(pItem->style), dwOldProtect, &tp);
}
}
}
pItem = pNextItem;
}
}
}
return true;
像LBS_OWNERDRAWFIXED
和LBS_SORT
这样的一些创建标志被缓存并且之后修改它们没有效果。您必须更改模板,或者只是复制列表框。复制旧列表框的样式,然后隐藏该列表框,更改其 ID,并在旧列表框的基础上创建一个新列表框。然后你必须删除 DDX_Control(pDX, IDC_LIST1, m_listbox)
下面的示例以设置了排序标志的标准列表开始。它复制列表框并禁用排序选项。
为简单起见,此示例避免使用 LBS_OWNERDRAWFIXED
,而是使用 LBS_SORT
。
class CMyPropertyPage : public CPropertyPage
{
public:
CListBox m_listbox;
int m_listbox_index;
CMyPropertyPage(int idd) : CPropertyPage(idd)
{
m_listbox_index = 1;
}
void DoDataExchange(CDataExchange* pDX)
{
//This function is automatically called before
//CPropertyPage::OnInitDialog is complete
//On the first call, IDC_LIST1 will point to the template listbox
if(m_listbox.m_hWnd)
{
//m_listbox is ready,
//IDC_LIST1 will refer to the new listbox
DDX_LBIndex(pDX, IDC_LIST1, m_listbox_index);
}
}
BOOL OnInitDialog()
{
CPropertyPage::OnInitDialog();
CListBox* old_listbox = (CListBox*)GetDlgItem(IDC_LIST1);
if(old_listbox)
{
DWORD style = ~LBS_SORT & GetWindowLongPtr(old_listbox->m_hWnd, GWL_STYLE);
CRect rc;
old_listbox->GetWindowRect(&rc);
ScreenToClient(&rc);
old_listbox->SetDlgCtrlID(0);//change the old ID to something unused
old_listbox->ShowWindow(SW_HIDE); //hide the old listbox
m_listbox.Create(style | WS_BORDER, rc, this, IDC_LIST1);
m_listbox.SetFont(GetFont());
}
ASSERT(m_listbox.GetDlgCtrlID() == IDC_LIST1);
m_listbox.AddString(L"2");
m_listbox.AddString(L"1");
m_listbox.AddString(L"0");
UpdateData(FALSE);
return TRUE;
}
};
class CMyWinApp : public CWinApp
{
BOOL InitInstance()
{
CWinApp::InitInstance();
CPropertySheet sh;
CMyPropertyPage page(IDD_PAGE1);
sh.AddPage(&page);
sh.DoModal();
return TRUE;
}
} myapp;
我正在修改一个现有的项目并且一个对话框有一些控件我在某些情况下将其子类化为不同的主题(在其他情况下我将完全不理会它)。在 DoDataExchange()
期间调用 DDX_Control()
时,ListBox
的 hwnd 已经应用了样式。具体来说,此时即使我做SetWindowLongPtr()
,LBS_OWNERDRAWFIXED
也不行。 "does not work," 我的意思是虽然应用了样式,但 CListBox 未收到所有者绘制消息。
相反,如果我避免使用 DDX_Control()
并简单地进行创建,ListBox 确实会收到消息并且可以是所有者绘制的。但是如果我这样做,现在有两个 HWND,GetDlgItem()
只返回其中一个。我相信如果有必要我可以完成这项工作,但我想知道是否有一个秘密可以拦截对话框中控件的 HWND 创建(实际上是一个 CPropertyPage)。
下面是不起作用的代码,如果可能的话,有更多注释的代码 "works" 但不是我希望它工作的方式。
void CMyPropertySheet::DoDataExchange(CDataExchange* pDX)
{
HWND hWndCtrl;
pDX->m_pDlgWnd->GetDlgItem(IDC_LIST1, &hWndCtrl);
if (themed) {
DWORD style = GetWindowLongPtr(hWndCtrl, GWL_STYLE) | LBS_OWNERDRAWFIXED;
SetWindowLongPtr(hWndCtrl, GWL_STYLE, style);
DDX_Control(pDX, IDC_LIST1, m_listbox);
//RECT wr;
//::GetWindowRect(hWndCtrl, &wr);
//m_listbox.Create(style, wr, this, IDC_LIST1);
} else {
DDX_Control(pDX, IDC_LIST1, m_listbox);
}
我可能应该补充一下,我尝试对 window 进行子类化,但没有帮助,而且 CMyPropertySheet::PreSubclassWindow
也不够快。
好的,我不确定我是否向任何人推荐这个,但我终于找到了修改模板的方法。我不得不使用 VirtualProtect 来解锁模板的内存。
for (int i = 0; i < m_pages.GetSize(); i++) {
CPropertyPage* pPage = GetPage(i);
PROPSHEETPAGE* tpsp = &pPage->m_psp;
const DLGTEMPLATE* pTemplate;
if (tpsp->dwFlags & PSP_DLGINDIRECT) {
pTemplate = tpsp->pResource;
} else {
HRSRC hResource = ::FindResource(tpsp->hInstance, tpsp->pszTemplate, RT_DIALOG);
if (hResource == NULL) return false;
HGLOBAL hTemplate = LoadResource(tpsp->hInstance, hResource);
if (hTemplate == NULL) return false;
pTemplate = (LPCDLGTEMPLATE)LockResource(hTemplate);
if (pTemplate == NULL) return false;
}
if (afxOccManager != NULL) {
DLGITEMTEMPLATE *pItem = _AfxFindFirstDlgItem(pTemplate);
DLGITEMTEMPLATE *pNextItem;
BOOL bDialogEx = IsDialogEx(pTemplate);
int iItem, iItems = DlgTemplateItemCount(pTemplate);
for (iItem = 0; iItem < iItems; iItem++) {
pNextItem = _AfxFindNextDlgItem(pItem, bDialogEx);
DWORD dwOldProtect, tp;
if (bDialogEx) {
_DialogSplitHelper::DLGITEMTEMPLATEEX *pItemEx = (_DialogSplitHelper::DLGITEMTEMPLATEEX *)pItem;
if (pItemEx->id == IDC_LIST1) {
if (VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), PAGE_READWRITE, &dwOldProtect)) {
pItemEx->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItemEx->style, sizeof(pItemEx->style), dwOldProtect, &tp);
}
}
} else {
if (pItem->id == IDC_LIST1) {
if (VirtualProtect(&pItem->style, sizeof(pItem->style), PAGE_READWRITE, &dwOldProtect)) {
pItem->style |= LBS_OWNERDRAWFIXED;
VirtualProtect(&pItem->style, sizeof(pItem->style), dwOldProtect, &tp);
}
}
}
pItem = pNextItem;
}
}
}
return true;
像LBS_OWNERDRAWFIXED
和LBS_SORT
这样的一些创建标志被缓存并且之后修改它们没有效果。您必须更改模板,或者只是复制列表框。复制旧列表框的样式,然后隐藏该列表框,更改其 ID,并在旧列表框的基础上创建一个新列表框。然后你必须删除 DDX_Control(pDX, IDC_LIST1, m_listbox)
下面的示例以设置了排序标志的标准列表开始。它复制列表框并禁用排序选项。
为简单起见,此示例避免使用 LBS_OWNERDRAWFIXED
,而是使用 LBS_SORT
。
class CMyPropertyPage : public CPropertyPage
{
public:
CListBox m_listbox;
int m_listbox_index;
CMyPropertyPage(int idd) : CPropertyPage(idd)
{
m_listbox_index = 1;
}
void DoDataExchange(CDataExchange* pDX)
{
//This function is automatically called before
//CPropertyPage::OnInitDialog is complete
//On the first call, IDC_LIST1 will point to the template listbox
if(m_listbox.m_hWnd)
{
//m_listbox is ready,
//IDC_LIST1 will refer to the new listbox
DDX_LBIndex(pDX, IDC_LIST1, m_listbox_index);
}
}
BOOL OnInitDialog()
{
CPropertyPage::OnInitDialog();
CListBox* old_listbox = (CListBox*)GetDlgItem(IDC_LIST1);
if(old_listbox)
{
DWORD style = ~LBS_SORT & GetWindowLongPtr(old_listbox->m_hWnd, GWL_STYLE);
CRect rc;
old_listbox->GetWindowRect(&rc);
ScreenToClient(&rc);
old_listbox->SetDlgCtrlID(0);//change the old ID to something unused
old_listbox->ShowWindow(SW_HIDE); //hide the old listbox
m_listbox.Create(style | WS_BORDER, rc, this, IDC_LIST1);
m_listbox.SetFont(GetFont());
}
ASSERT(m_listbox.GetDlgCtrlID() == IDC_LIST1);
m_listbox.AddString(L"2");
m_listbox.AddString(L"1");
m_listbox.AddString(L"0");
UpdateData(FALSE);
return TRUE;
}
};
class CMyWinApp : public CWinApp
{
BOOL InitInstance()
{
CWinApp::InitInstance();
CPropertySheet sh;
CMyPropertyPage page(IDD_PAGE1);
sh.AddPage(&page);
sh.DoModal();
return TRUE;
}
} myapp;