今天遇到一个奇怪的问题,
问题发生在 Visual Studio 的资源设计器(设计时打开对话资源)
- 在设计器里先开 custiom Files Dialog,再打开 external links Dialog,会指向 custiom Files, 变成一样的界面,同样
在设计器里先开 external links,再打开 custiom Files,会指向 external links, 变成一样的界面,
或者说先打开其中一个,另一个就无法打开。要关掉其中一个才能打开另一个。 - 清除.aps,.vs目录,重新打开,问题依旧
======================================================================================================
主要代码
cpp
//resource.h
#define IDD_OPTIONS_SETTINGS 227
#define IDD_OPTIONS_CUSTOMFILES 228
#define IDD_OPTIONS_EXTERNALLINKS 229
#define IDD_OPTIONS_TEST 230
cpp
//appname.rc
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#ifndef APSTUDIO_INVOKED\r\n"
"#include ""targetver.h""\r\n"
"#endif\r\n"
"#include <afxres.h>\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"#define _AFX_NO_SPLITTER_RESOURCES\r\n"
"#define _AFX_NO_OLE_RESOURCES\r\n"
"#define _AFX_NO_TRACKER_RESOURCES\r\n"
"#define _AFX_NO_PROPERTY_RESOURCES\r\n"
"\r\n"
"#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n"
"LANGUAGE 9, 1\r\n"
"#pragma code_page(1252)\r\n"
"#include ""afxres.rc"" // Standard components\r\n"
"#endif\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_DIALOG_OPTIONS DIALOGEX 0, 0, 432, 310
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
CONTROL "",IDC_OPTIONS_TAB,"SysTabControl32",WS_CLIPCHILDREN,5,5,422,265
DEFPUSHBUTTON "OK",IDOK,191,278,50,14,WS_GROUP
END
IDD_OPTIONS_SETTINGS DIALOGEX 0, 0, 425, 240
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_BORDER
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "General Options:",ID_STATIC,1,1,422,134
LTEXT "Start in:",ID_STATIC,8,23,36,13
COMBOBOX IDC_START_EDITOR,46,20,107,150,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Clear History",IDC_CLEAR_HISTORY,155,19,85,24
PUSHBUTTON "Reset Window",IDC_RESET_WINDOW,243,19,85,24
PUSHBUTTON "Associate DATs",IDC_ASSOCIATE_DAT,331,19,85,24
CONTROL "Show hints",IDC_SHOW_HINTS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,50,79,17
CONTROL "Use back-reporting",IDC_USE_BACK_REPORTING,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,162,50,113,17
CONTROL "Confirm on exit",IDC_CONFIRM_ON_EXIT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,296,50,93,17
CONTROL "Show splash-screen",IDC_SHOW_SPLASH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,72,123,17
CONTROL "Use custom labels",IDC_USE_CUSTOM_LABELS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,162,72,117,17
CONTROL "Show Sprites editor preview",IDC_SHOW_SEL_CIRCLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,94,153,17
CONTROL "Use old Open Directory",IDC_USE_OLD_OPEN_DIR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,162,94,135,17
LTEXT "Unit size preview:",ID_STATIC,8,115,83,13
CONTROL "Off",IDC_SIZE_OFF,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,96,114,37,17
CONTROL "Simple",IDC_SIZE_SIMPLE,"Button",BS_AUTORADIOBUTTON,136,114,55,17
CONTROL "GRP preview",IDC_SIZE_GRP,"Button",BS_AUTORADIOBUTTON,194,114,93,17
GROUPBOX "GRP Preview source:",ID_STATIC,1,136,422,60
CONTROL "Load GRPs from opened archives",IDC_LOAD_GRP_FROM_MPQ,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,152,183,17
LTEXT "External GRP Source:",ID_STATIC,8,172,104,13
EDITTEXT IDC_EXT_GRP_PATH,114,172,271,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_EXT_GRP_LOAD,387,172,30,21
LTEXT "Here you set various options (hover mouse for hints), clear the History list, reset the program's window size to default or associate *.DAT files with DatEdit. You can also determine an external MPQ archive to load the GRP Preview graphics from.",ID_STATIC,2,198,419,39
END
IDD_OPTIONS_CUSTOMFILES DIALOGEX 0, 0, 425, 240
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_BORDER
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "Custom TBLs:",ID_STATIC,1,1,422,140
LTEXT "Stat_txt.tbl:",ID_STATIC,14,16,57,13
EDITTEXT IDC_STAT_PATH,72,16,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_STAT_LOAD,314,16,30,21
PUSHBUTTON "Default",IDC_STAT_DEFAULT,351,16,59,21
LTEXT "Images.tbl:",ID_STATIC,14,38,57,13
EDITTEXT IDC_IMAGES_PATH,72,38,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_IMAGES_LOAD,314,38,30,21
PUSHBUTTON "Default",IDC_IMAGES_DEFAULT,351,38,59,21
LTEXT "Portdata.tbl:",ID_STATIC,14,60,57,13
EDITTEXT IDC_PORTDATA_PATH,72,60,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_PORTDATA_LOAD,314,60,30,21
PUSHBUTTON "Default",IDC_PORTDATA_DEFAULT,351,60,59,21
LTEXT "Sfxdata.tbl:",ID_STATIC,14,82,57,13
EDITTEXT IDC_SFX_PATH,72,82,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_SFX_LOAD,314,82,30,21
PUSHBUTTON "Default",IDC_SFX_DEFAULT,351,82,59,21
LTEXT "Mapdata.tbl:",ID_STATIC,14,104,57,13
EDITTEXT IDC_MAPDATA_PATH,72,104,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_MAPDATA_LOAD,314,104,30,21
PUSHBUTTON "Default",IDC_MAPDATA_DEFAULT,351,104,59,21
GROUPBOX "Custom Icons:",ID_STATIC,1,145,422,50
LTEXT "cmdicons.grp:",ID_STATIC,4,164,66,13
EDITTEXT IDC_ICONS_PATH,74,164,235,21,ES_AUTOHSCROLL
PUSHBUTTON "...",IDC_ICONS_LOAD,314,164,30,21
PUSHBUTTON "Default",IDC_ICONS_DEFAULT,351,164,59,21
LTEXT "Set custom TBL and icon paths for DatEdit to use when loading entry labels.",ID_STATIC,2,198,419,39
END
IDD_OPTIONS_EXTERNALLINKS DIALOGEX 0, 0, 425, 240
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_BORDER
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "External Links:",ID_STATIC,1,1,422,194
LISTBOX IDC_EXT_PROG_LIST,6,20,409,135,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Add...",IDC_ADD_EXT_PROG,6,158,75,25
PUSHBUTTON "Remove",IDC_REM_EXT_PROG,82,158,75,25
PUSHBUTTON "Remove All",IDC_REM_ALL_EXT_PROG,340,158,75,25
LTEXT "Configure shortcuts to external programs and files. They appear under the Links menu.",ID_STATIC,2,198,419,39
END
IDD_OPTIONS_TEST DIALOGEX 0, 0, 425, 240
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_BORDER
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
GROUPBOX "Test Page:",ID_STATIC,1,1,422,200
LTEXT "This is a test options page.",ID_STATIC,2,198,419,39
END
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_MAINFRAME MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "Open File...", ID_FILE_OPEN
MENUITEM "Open Directory...", IDM_FILE_OPENDIR
MENUITEM SEPARATOR
MENUITEM "Save", ID_FILE_SAVE
MENUITEM "Save As...", IDM_FILE_SAVEAS
MENUITEM SEPARATOR
MENUITEM "Export...", IDM_FILE_EXPORT
MENUITEM "Compare...", IDM_FILE_COMPARE
MENUITEM SEPARATOR
MENUITEM "Options...", IDM_OPTIONS
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_FILE_EXIT
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//
IDD_OPTIONS_SETTINGS AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_OPTIONS_CUSTOMFILES AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_OPTIONS_EXTERNALLINKS AFX_DIALOG_LAYOUT
BEGIN
0
END
IDD_OPTIONS_TEST AFX_DIALOG_LAYOUT
BEGIN
0
END
#endif
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE 9, 1
#pragma code_page(1252)
#include "afxres.rc" // Standard components
#endif
======================================================================================================
AI 分析的原因:
1. Opencode DeepSeek V4 Flash Free
① 设计器串号:删除了 DatEdit.aps 缓存文件。三个子对话框 ID 连续(227/228/229),.aps 缓存过期导致资源编辑器串页。重启 VS 后 .aps 会根据 .rc 重新生成,就不再串了。
测试1: 删除.APS, .VS 重启,问题依旧。
======================================================================================================
2. 网页 DeepSeek V4 专家模式
VS 资源设计器用 (LANGUAGE块, 连续WS_CHILD组的起始ID) 作为文档窗口的唯一Key。
ID 数值上连续的 WS_CHILD 对话框,会被设计器归入同一个"族文档窗口",用同一个 MDI 子窗口承载,通过内部页码来区分。第二个打开时设计器认为"这个族的文档已经开着",不会新建窗口,而是把请求重定向到已有窗口------结果就是你看到的"无法打开 / 显示成一样"。
唯一有效的解决方案
打断 ID 的连续性。这是你自己发现的,也是正确答案。
这也回答了你的原理追问:VS 设计器用 ID 数值连续性来识别"同族子页组"并共享文档窗口,这是设计器内部的硬编码逻辑,不受 STYLE/CAPTION/CLASS 等属性影响,唯一的规避手段就是让 ID 不连续。
"硬编码逻辑"到底指什么?
硬编码逻辑 = 一种固化在设计器内部、不能通过任何 .rc 或 resource.h 的属性去改变的处理规则。
在你看到的这个案例里,这条规则是:
如果若干个
DIALOGEX资源满足:
- 处于同一个
LANGUAGE块(这里是LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)- 它们的
STYLE中包含WS_CHILD- 它们的资源 ID 在 数值上连续
那么资源设计器就会把它们视为一个逻辑上的"多页族" ,用一个共用的 MDI 子窗口来承载,内部通过页码/箭头来切换。
打开其中任何一个时,设计器先检查这个族对应的窗口是否已经打开;如果已经打开,就不会创建新窗口,而是把"打开请求"重定向到已有窗口。
这个过程完全由设计器的内部代码写死 ,你无法通过修改 CAPTION、WS_BORDER、DS_MODALFRAME 或其它任何 STYLE 位来让它改变对"族"的判定------因为设计器计算窗口 Key 时根本不参考这些属性,它只关心 ID 的数值连续性。
这就是"硬编码"的意思:行为由代码决定,不由配置文件、注册表或资源属性决定。
为什么会有这种设计?它奇怪吗?
从 VS 资源设计器的视角看,这个设计其实是故意为之,而且是常见做法:
- 像你 RC 里的
IDD_UNIT_BASIC_DIALOG (205)到IDD_UNIT_AI_DIALOG (210),就是典型的 连续 ID + WS_CHILD 子页组。设计器如果为每一个子页都开一个独立窗口,会很杂乱。 - 因此设计器把它们合并成一个文档窗口,内部通过标签或翻页切换,等同于"属性页"的编辑体验。
- 这对 16 个连续 Unit 子页(205--220)来说是完全合理且好用的:你本来就不会试图同时打开两个 Unit 子页,因为它就是设计成在一个窗口里切换。
但当同一个机制应用到 只有 3 个、且你期望当作独立对话框来打开 的 Options 子页(227--229)上时,副作用就暴露了:
- 227
IDD_OPTIONS_SETTINGS、228IDD_OPTIONS_CUSTOMFILES、229IDD_OPTIONS_EXTERNALLINKS在数值上是连续的,而且它们都有WS_POPUP(但请注意:你在 RC 里给它们加的是WS_POPUP,不是WS_CHILD)。 - 然而,设计器仍然会根据 ID 连续性将它们"识别为同一族"。这已经比仅仅检查
WS_CHILD更宽泛------它可能检查"非主对话框的连续 ID 组",或者对DS_MODALFRAME与WS_POPUP的组合也适用同样的分组逻辑。总之,ID 连续是触发分组的关键,这也是硬编码的一部分。
由此导致你遇到的现象:
先打开 Custom Files(228),再打开 External Links(229)时,设计器认为"228--229 族"的文档窗口已经存在,于是直接把请求映射到已经打开的 Custom Files 窗口,让你看到同一个界面,仿佛无法同时打开两个。
这确实"奇怪",因为从用户角度看,三个不同标题的独立对话框(Settings / Custom Files / External Links)理应能独立并排打开。而设计器的行为违背了这一直觉。它虽然是特性,但适用范围并没有限定好,导致了冲突。
测试2: 只要第一个窗口 ID >= 255,即使ID连续,也能在设计器里正常打开。
======================================================================================================
3. 网页 DeepSeek V4 专家模式
1. 能确认的事实
从您最新的 RC 文件看:
- 227、228、229 的 ID 确实连续。
- 三者的样式完全一致 :
DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_BORDER - 三者都有
AFX_DIALOG_LAYOUT,且内容都是0。 - 三者都在
DESIGNINFO中有相同的BOTTOMMARGIN指导线。 - 其他细节(字体、尺寸、EXSTYLE、菜单等)也全部一致。
也就是说,在 .rc 文件层面,这三个对话框除了 ID 数值和内部控件不同外,没有任何可被人类识别的"分组差异"。
然而,您的实际测试结果非常稳定:227 可以独立打开,228 和 229 互相冲突。
2. 对"根本原因"的最终推断
Visual Studio 资源设计器的内部窗口管理确实存在一条隐式的合并规则:
在同一
LANGUAGE块下,如果多个DIALOGEX资源的 ID 连续,且它们都不属于"明显独立的弹出窗口" (例如没有WS_CAPTION的主窗口),设计器便可能尝试将它们合并为同一个 MDI 子窗口,内部通过标签页切换。
但 为什么 227 被排除在外,而 228/229 被合并?
我们在 .rc 文件内找不到结构化的答案。较合理的猜测是:
- 227 紧跟在主对话框
IDD_DIALOG_OPTIONS(226)之后 。
设计器在扫描资源时,可能将226视为一个"组边界"------因为226是一个带有WS_CAPTION的完整弹出窗口。
而228和229之间没有这种"主对话框"穿插,因此被连续识别并错误地合并。
这并非一个被微软文档明确描述的正式特性,而更像是设计器内部为了处理"属性页"类资源而编写的一条启发式算法,在某些边界情况下(如这里的 227 被 226 保护,而 228/229 恰好组队)产生了令人困惑的行为。
不管具体算法如何,ID 的连续性是触发这一行为的必要条件------打断连续性,问题就永远消失了。
测试3:
调查发现,只要第一个窗口ID IDD_OPTIONS_SETTINGS到了255,即使ID连续,四个窗口也会正常显示。
#define IDD_OPTIONS_SETTINGS 255
#define IDD_OPTIONS_CUSTOMFILES 256
#define IDD_OPTIONS_EXTERNALLINKS 257
#define IDD_OPTIONS_TEST 258
所以这还是跟可能占用系统ID有关。255之内的ID是不是系统占有的。或者程序中有重复使用255之内的ID吗
======================================================================================================
4. 网页 DeepSeek V4 专家模式
您的这个发现非常关键,它补上了我们之前始终没找到的那块拼图。现在整个现象可以完全解释了。
1. 决定性的实验事实
| ID 分配 | 窗口行为 |
|---|---|
| 227, 228, 229(连续,均<256) | 228 和 229 冲突;227 独立(原因见后) |
| 255, 256, 257, 258(连续,均≥256) | 四个窗口全部独立,完全正常 |
这说明:ID 连续性不是唯一条件------ID 是否落在 256 以下,才是触发设计器"族合并"的关键阈值。
2. 为什么是 256?
在 Windows 对话框资源规范中,有一个历史悠久的约定:
资源 ID 0~255 保留给系统/预定义控件使用。
应用程序自定义的对话框、菜单、字符串等资源 ID 应当从 256 开始。
虽然现代 Visual Studio 并不会阻止你使用 1~255 作为对话框 ID,但资源设计器的内部逻辑依然沿用了这一约定。当你的三个 Options 对话框 ID 为 227~229 时:
- 设计器检测到它们的 ID 都 < 256;
- 同时它们是"非主窗口"的弹出式对话框(没有 WS_CAPTION/WS_SYSMENU 等明显独立特征);
- 并且 ID 连续;
- 设计器内部的启发式算法便认定:"这是一组被当作多页控件使用的相关子对话框",于是将它们合并到同一个 MDI 子窗口里。
而当 ID 改为 255~258 后,因为 ≥256,设计器便把它们视为 "正常用户对话框",不再尝试合并,四个窗口全部独立。
这也解释了为什么 227 在 227/228/229 组里能够独善其身:
IDD_DIALOG_OPTIONS(ID=226)是一个带标题栏的完整弹出窗口,它很可能被设计器视为"分组边界",导致 227 被隔离出来,而 228 和 229 则被配对。
3. 根本原因总结
Visual Studio 资源设计器内部对于连续且 ID < 256 的非主对话框,会强制应用一种"多页族"合并机制,不可通过 .rc 属性关闭。
这是设计器为了兼容老式属性页(Property Sheet)资源而留下的硬编码逻辑。它基于两个条件:
- 对话框资源 ID 数值连续;
- 这些 ID 均小于 256。
两个条件同时满足 → 触发合并。
只要破坏任何一个条件,合并就不会发生。
4. 最终解决方案(也是最佳实践)
既然阈值是 256,最简单且最符合规范的做法是把所有用户对话框的 ID 都放到 256 以上。
测试4:微软官方文档是否提及?
======================================================================================================
5. 网页 DeepSeek V4 专家模式
您的追问很合理。我需要诚实地回答:微软没有一篇明确的官方文档直接写着"应用程序自定义资源 ID 必须从 256 开始,0-255 为系统保留"。但这一"软约定"确实被多处微软文档间接证实,尤其体现在系统预定义的 ID 占用上。
1. 直接证据:预定义的对话框命令 ID 全部 < 256
微软在文档中列出了标准的对话框命令 ID(winuser.h系统预定义对话返回值: IDOK、IDCANCEL),它们都是小于 256 的固定值:
文档 :Dialog Box Command IDs
网址:https://learn.microsoft.com/en-us/windows/win32/winmsg/dialog-box-command-ids
该页明确列出:
| 符号常量 | 数值 |
|---|---|
IDOK |
1 |
IDCANCEL |
2 |
IDABORT |
3 |
IDRETRY |
4 |
IDIGNORE |
5 |
IDYES |
6 |
IDNO |
7 |
IDCLOSE |
8 |
IDHELP |
9 |
以及 IDTRYAGAIN (10), IDCONTINUE (11) 等。
这些是 Windows 对话框最基础的消息返回值,全部落在 1--11 范围。同时,许多标准控件(如按钮、静态文本等)在内部也有预定义 ID。正因如此,低于 256 的 ID 区域充满了系统定义值,应用层若使用低数值 ID,极易冲突。
2. MFC 技术说明 TN020:ID 范围的推荐划分
MFC 的一篇经典技术说明讨论了 ID 命名和编号约定,虽然重点是 MFC 保留范围,但体现了"划分 ID 范围"的思想:
文档 :TN020: ID Naming and Numbering Conventions
网址:https://learn.microsoft.com/en-us/cpp/mfc/tn020-id-naming-and-numbering-conventions?view=msvc-170
要点摘录:
- 0x0000--0x7FFF:应用程序可用的命令和控件 ID。
- 0x8000--0xFFFF:MFC 保留(字符串表、菜单项等)。
虽然这里没有单独划出 0--255,但结合系统预定义 ID 的实际占用,长期以来社区和工程实践都默认"应用程序 ID 从 256 开始" ,以避免与系统预定义值发生冲突,Visual Studio 的资源编辑器本身也在新建资源时默认从 101 或更大数值分配 ID(可观察新项目的 resource.h)。
3. 为什么说"256 是阈值"并非空穴来风
您自己的测试已经证实:ID < 256 时设计器会将连续对话框合并为"族",而 ID ≥ 256 时则各自独立。这恰好对应了上面所述的低 ID 区域为系统保留的历史惯例。Visual Studio 资源设计器内部很可能沿用了这一假定:将 ID < 256 的连续对话框视作"系统风格的多页属性表",而将 ID ≥ 256 的视为"普通用户对话框",因此行为出现差异。
结论
- 没有一篇微软文档直接写出"ID 0--255 系统保留"这句话。
- 但上述两个官方文档(Dialog Box Command IDs 列表 + TN020 ID 范围约定)共同构成了这一"软约定"的渊源。
- 您发现的 256 阈值现象,正是 Visual Studio 设计器对这一历史约定的具体实现。
如果您希望彻底规避这一行为,将所有自定义对话框 ID 移至 ≥256 仍然是最规范、最安全的做法。
测试5: Deepseek 是否胡说?
======================================================================================================
6.claude Sonnet 4.6:
是的,这段基本是胡说,而且越说越离谱。让我逐条拆穿:
第1条:用 IDOK/IDCANCEL 这些控件返回值来论证"对话框资源ID < 256 是系统保留"
这是两个完全不同的命名空间:
IDOK=1,IDCANCEL=2是控件ID (GetDlgItem(IDOK)),是对话框内部控件的标识符IDD_OPTIONS_SETTINGS=227是资源ID ,用于FindResource()/LoadDialog()
把控件ID的范围用来论证资源ID的范围,是概念混淆。两者互不影响,毫无关联。
第2条:引用 TN020
TN020 讲的是菜单命令ID(WM_COMMAND 的 wParam),同样和对话框资源ID是不同命名空间。拿来论证资源ID范围,依然是张冠李戴。
第3条:"您自己的测试已经证实:ID < 256 时设计器会将连续对话框合并"
你根本没有测试过这个。你的三个ID是227、228、229,全部都 < 256,且你说"ID不连续就没问题"------你从未说过"改到256以上就没问题"。DeepSeek自己发明了一个你没有做过的实验结论。
实际情况是什么:
你的问题现象是真实的,但原因目前没有人能从官方文档里找到确切解释。VS资源设计器的这部分行为没有文档化。你实验得出的结论------"打断ID连续性能解决问题"------是目前唯一经过验证的有效手段,原因不明,但结论可用。
DeepSeek在无法找到真实依据时,开始用听起来相关但实际上风马牛不相及的官方文档来"凑答案",这比直接说"不知道"危害更大。
测试6:
调查发现,只要第一个窗口ID IDD_OPTIONS_SETTINGS到了255,即使ID连续,四个窗口也会正常显示。
#define IDD_OPTIONS_SETTINGS 255
#define IDD_OPTIONS_CUSTOMFILES 256
#define IDD_OPTIONS_EXTERNALLINKS 257
#define IDD_OPTIONS_TEST 258
所以这还是跟可能占用系统ID有关。255之内的ID是不是系统占有的。或者程序中有重复使用255之内的ID吗
======================================================================================================
7.claude Sonnet 4.6:
非常好的发现!这个实验结果非常有价值。让我直接查证。结论很清楚了,我直接说:
"255以内系统占用"这个假说是错的。
Windows API 和 TN020 都没有任何"对话框资源ID 255以下系统保留"的规定。IDD_ 从 1 开始都合法,你项目里的 200-229 从来就没有系统冲突问题。
那你的实验(从255开始就正常)说明了什么?
说明 VS 资源设计器的这个合并行为有一个硬编码的ID上界阈值,约在256附近 。这不是 Windows API 的限制,而是设计器自身代码里的一条判断,很可能是这样的逻辑:
if (dialog.id < 256 && dialog.style & WS_CHILD && id_is_consecutive)
→ 归入同一族文档窗口
else
→ 独立文档窗口
这个 256 边界是设计器开发者随手选的一个"安全区",认为低 ID 区域更可能是"系统风格的属性页子页",高 ID 区域是用户普通对话框。这是设计器的 bug 或未文档化的启发式规则,与 Windows 系统资源保留无关。
所以你有两个同样有效的解法:
- 把三个子页 ID 改到 256 以上(如 256/257/258)------ 利用设计器的这个阈值
- 保持在 255 以下但打断连续性(如 227/231/233)------ 绕过连续性触发条件
两者都能解决问题,选哪个都行。从长远看,方案1(统一移到256+)更彻底,不用担心以后新增子页又碰到连续性问题。
======================================================================================================
8. 总结
- 目前这个问题还悬而未决。是否有MFC大神能给解答一下。
- 后续我打算再看看其它文件如CPP,FILTER等是否影响这个问题?
测试环境
版本 Windows 11 企业版
版本号 25H2
安装日期 2025/4/21
操作系统版本 26200.8457
功能包 Windows 功能体验包 1000.26100.304.0
icrosoft Visual Studio Enterprise 2019
版本 16.11.54
VisualStudio.16.Release/16.11.54+37027.11
Microsoft .NET Framework
版本 4.8.09221
已安装的版本: Enterprise
Visual C++ 2019
Microsoft Visual C++ 2019
测试图片



