2

谁能指出我使用 WINAPI(不是 MFC)在对话框中嵌入属性表的 WINAPI 示例?

4

1 回答 1

2

作为记录,以下是我在调查这个问题时学到的一些东西。我通过在这里提问或通过搜索网络获得了大部分/所有这些。感谢大家的帮助!

我写了一个类来封装我发现的所有东西;如果有兴趣获得一份副本,请给我发送电子邮件 mdorl@wisc.edu。

使用 PropertySheet 页面函数创建属性表时,在 PROPSHEETHEADER 的 .hwndParent 中为您的属性表传递父窗口的 HWND。

我在对话框上使用了一个图片控件作为属性表的父级,所以这就是我用作 hwndParent 的 HWND。

使用 header 中的回调 pfnCallback 字段更改属性表的样式,不要忘记在 dwFlags 中包含 PSH_USECALLBACK。请参阅 MSDN 中的 PropSheetProc 函数。在回调的 PSCB_PRECREATE 处理程序中,通过删除 WS_POPUP 并添加 WS_CHILD 来调整属性表的窗口样式。我不想要属性表上的边框或标题栏,所以我还删除了 WS_CAPTION、WS_SYSEMNU 和 DS_MODALFRAME

LONG L = ((LPDLGTEMPLATE)lParam)->style;
L &= ~WS_POPUP; 
L &= ~WS_CAPTION;       // gets rid of title bar
L &= ~WS_SYSMENU;       
L &= ~DS_MODALFRAME;        // gets rid of border
L |=  WS_CHILD;         // 40000000
((LPDLGTEMPLATE)lParam)->style = L;

使用子窗口而不是弹出窗口巧妙地解决了一些问题,例如在父窗口移动时移动属性表、保持父窗口的蓝色/灰色状态正确着色以及剪切问题。即你不必担心这些事情。

如果你停在这里,你将面临另一个问题。WIN32 中的对话框支持代码将进入 cpu 循环,试图在其 Windows 中查找属性表和控件。看

WS_EX_CONTROLPARENT 和 DS_CONTROL 是干什么用的?

对于这个问题的一个很好的解释。解决方案是将 WS_EX_CONTROLPARENT 扩展样式添加到属性表中。您可以通过搜索 WS_EX_CONTROLPARENT 在 Web 上找到相关信息。 你找不到的东西让我很伤心。如果将对话框上的控件用作父控件,则还必须设置其 WS_EX_CONTROLPARENT。

我不知道 WS_EX_CONTROLPARENT 和 DS_CONTROL 之间的区别是什么,也不知道您是否可以用 DS_CONTROL 代替 WS_EX_CONTROLPARENT。从网上我收集到 DS_CONTROL 与标签有关。对于有或没有 DS_CONTROL 的选项卡,我的测试应用程序的工作方式相同。

在调用 PropertySheet 函数创建属性表后,我处理了 WS_EX_CONTROLPARENT 业务。我不知道您是否可以在回调例程中执行此操作。我想您可以在回调中使用 GetParent 来获取控件的 HWND。

LONG S;

S = GetWindowLong (hwndPS, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS, GWL_EXSTYLE, S);

S = GetWindowLong (hwndPS_Area, GWL_EXSTYLE) | WS_EX_CONTROLPARENT;
SetWindowLong (hwndPS_Area, GWL_EXSTYLE, S);

使用 PropertySheet 函数创建属性表后,您必须正确定位它:

SetWindowPos(hwndPS, HWND_TOP, 2, 2, -1, -1,
             SWP_NOSIZE | SWP_NOACTIVATE); 

我为边框设置了几个像素。

如果您想摆脱所有属性表按钮及其占用的空间:

RECT rectWnd;
RECT rectButton;

GetWindowRect(hwndPS, &rectWnd);
HWND hWnd = ::GetDlgItem(hwndPS, IDOK);

if(!hWnd)
{
    DebugBreak();
}

GetWindowRect(hWnd, &rectButton);

SetWindowPos (hwndPS, NULL, 0, 0,
          rectWnd.right - rectWnd.left, rectButton.top - rectWnd.top,
          SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);

可以消除单个按钮:

hwndOk = GetDlgItem (hwndPS,IDOK);              // Hide the OK button
ShowWindow (hwndOk, SW_HIDE);
EnableWindow (hwndOk, FALSE);

创建属性表时,您可以使用 PROPERTYSHEETHEADER 中的 PSH_NOAPPLYNOW dwFlags 去掉 APPLY 按钮。

在用户第一次激活它们之前,不会创建属性表页面。我希望在创建属性表时创建它们。

unsigned int iP;
for (iP=0; iP<Header.nPages; iP++)
{
    if (iP != Header.nStartPage)
        SendMessage (hwndPS, PSM_SETCURSEL,iP,NULL);
}
SendMessage (hwndPS, PSM_SETCURSEL,Header.nStartPage,NULL);

有一个 PROPSHEETPAGE 标志可以做同样的事情(来自 MSDN)

PSP_PREMATURE 版本 4.71。导致在创建属性表时创建页面。如果未指定此标志,则在第一次选择之前不会创建页面。

但我担心版本 4.71 的业务,所以我自己做了。

您可以更改任何按钮上的文本:

SetDlgItemText (hwndPS,ButtonID,Text);

其中 ButtonID 是 IDOK IDCANCEL IDHELP IDAPPLYNOW 之一

IDAPPLYNOW 在我的系统上未定义,所以

#define IDAPPLYNOW     0x3021   

这是我打算如何使用我的属性表的方式。我将把所有初始化和终止代码放在包含属性表的对话框中,并消除所有属性表按钮。当用户按下某个 DO IT 按钮时,我可以在那里设置所有初始值并在那里检索所有最终值。我注意到设置行为,例如,一个文本框会导致包含页面对话框获得 WM_COMMAND/EN_CHANGE。如果您使用此消息启用 APPLY 按钮,您可能需要在设置任何列表框后禁用页面的更改标志。我通过在设置初始值后清除所有更改的标志来解决这个问题。(我怀疑在发送了所有 INITDIALOG 消息后,属性表会清除更改的标志。在任何情况下,如果在页面 INITDIALOG 处理程序中设置了文本框,则不会启用 APPLY。)如果我发现错误,我将选择该属性表页面并在父对话框中放置一些红色文本来描述错误。属性表对话框程序唯一需要做的就是操作它们的控件。

令我困惑的一件事是如何确定是否所有页面都从其 WS_NOTIFY/PSN_APPLY 消息中返回了 PSNRET_NOERROR。当报告 PSNRET_INVALID 或收到 PSN_KILLACTIVE 时,我已经成功计算了连续 PSNRET_NOERROR 回复的数量,将计数设置为零。如果计数达到属性表标题中的页数,我假设所有页面都返回了 PSNRET_NOERROR。但我担心诸如禁用和不可见页面之类的事情。

于 2011-07-24T12:39:03.917 回答