作为在用C编写的旧Windows WinAPI GUI应用程序中扩展对话框功能的一部分,我再次面临为六行数据输入对话框的每一行添加多个复选框。我无法忍受重复资源文件和源代码文件更改的麻烦,并决定从Java UI借用UI设计方法从窗格构建UI。
Visual Studio工具,至少是Visual Studio 2005,似乎不鼓励这种方法,在这种情况下,我手动编辑了资源文件。也许Visual Studio 2017的资源编辑工具更灵活。
我的问题是这种方法的替代方案似乎很容易做,并且更符合Visual Studio的理念。
我也想知道这种方法的缺点。
对于使我烦恼的Visual Studio C WinAPI GUI应用程序,这种方法似乎不常见。我不能声称自己特别具有创新性,所以我想知道我错过了什么,因为这种方法似乎至少在对资源文件进行手工编辑时效果很好。
我正在考虑进行另一次迭代,其中我将重复的每一行的控件列表移动到无模式对话框模板中,并且让原始对话框成为6个静态窗口的堆栈,每行一个。
这种方法的好处是定义更少,并且能够重用定义。将新功能插入现有的对话行为源代码也更容易,尽管这主要是因为这些只是简单的自动复选框。
我看到的一个问题是在执行此更改后使用Visual Studio工具。但是,此特定应用程序的资源文件无论如何都无法与Visual Studio资源编辑工具一起使用。
当我需要向无模式对话框模板添加一些额外的复选框时,这种方法已经有了回报。我必须做的资源文件更改是将新的对话框模板添加其他复选框,并调整原始对话框大小,无模式对话框大小和静态窗口的大小,以使所有内容都可见。
实施
我实施的替代方案是:
该对话框的新版本看起来像当显示原始对话框时,init对话框消息的处理程序创建一组六个无模式对话框,每个新添加的静态窗口一个,对话框的父窗口是静态的窗口。这将无模式对话框置于静态窗口中,当静态窗口移动时,无模式对话框也会移动。
所有六个无模式对话框都使用相同的对话框消息处理程序。消息处理程序本身不处理任何消息。
无模式对话框模板是:
IDD_A170_DAYS DIALOG DISCARDABLE 0, 0, 240, 20
STYLE WS_CHILD | WS_VISIBLE
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "Ovr",IDD_A170_STR1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,1,25,10
CONTROL "AND",IDD_A170_STR2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,35,1,40,10
CONTROL "S",IDD_A170_CAPTION1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,75,1,20,10
CONTROL "M",IDD_A170_CAPTION2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,100,1,20,10
CONTROL "T",IDD_A170_CAPTION3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,125,1,20,10
CONTROL "W",IDD_A170_CAPTION4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,1,20,10
CONTROL "T",IDD_A170_CAPTION5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,175,1,20,10
CONTROL "F",IDD_A170_CAPTION6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,195,1,20,10
CONTROL "S",IDD_A170_CAPTION7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,220,1,20,10
END
和静态窗口的主对话框是:
IDD_A170 DIALOG DISCARDABLE 2, 17, 530, 190
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Set Sales Code Restriction Table of PLU (AC 170)"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "Address (PLU Sales Code)",IDD_A170_CAPTION1,14,10,64,20
LTEXT "Date",IDD_A170_CAPTION2,86,14,28,12
LTEXT "Day of week",IDD_A170_CAPTION3,115,10,33,21
LTEXT "Start hour",IDD_A170_CAPTION4,153,10,20,18
LTEXT "Minute",IDD_A170_CAPTION5,182,14,26,12
LTEXT "End hour",IDD_A170_CAPTION6,217,10,20,18
LTEXT "Minute",IDD_A170_CAPTION7,245,14,26,12
LTEXT "Override/Type",IDC_STATIC,290,14,50,12
LTEXT "Days To Restrict",IDC_STATIC,390,14,100,12
LTEXT "",IDD_A170_STR1,8,34,74,12 // first control on line 1
EDITTEXT IDD_A170_DATE1,87,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_DATESPIN1,104,33,8,12,SBS_VERT
EDITTEXT IDD_A170_WEEK1,119,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_WEEKSPIN1,136,33,8,12,SBS_VERT
EDITTEXT IDD_A170_SHOUR1,151,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_SHOURSPIN1,168,33,8,12,SBS_VERT
EDITTEXT IDD_A170_SMINUTE1,183,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_SMINUTESPIN1,200,33,8,12,SBS_VERT
EDITTEXT IDD_A170_EHOUR1,214,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_EHOURSPIN1,231,33,8,12,SBS_VERT
EDITTEXT IDD_A170_EMINUTE1,246,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_EMINUTESPIN1,263,33,8,12,SBS_VERT
LTEXT "D1",IDD_A170_DAYS_1,281,33,240,20 // static window to contain the modeless dialog box from the template IDD_A170_DAYS above
// .. repeated sequence for 5 more lines
CONTROL "MDC 298 - Sales Restriction Type is AND",IDD_A170_MDC_PLU5_ADR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,140,170,9
LTEXT "[Address : 1 - 6, Date : 0 - 31",IDD_A170_CAPTION8,9,154,99,9
LTEXT "Day of week : 0 - 7 (1 - Sunday, 7 - Saturday)]",IDD_A170_CAPTION9,110,154,167,9
LTEXT "[Hour : 0 - 24, Minute : 0 - 59 (For 0:00, enter 24:00)]",IDD_A170_CAPTION10,9,168,167,9
PUSHBUTTON "&Ok",IDOK,285,154,48,20
PUSHBUTTON "&Cancel",IDCANCEL,345,154,48,20
END
您可能会注意到我刚刚在新的无模式对话框中重用了一些已在原始对话框中使用的定义。我能够这样做是因为控件标识符特定于对话框本身。因此,在不同的对话框中使用相同的定义不会导致问题,因为使用GetDlgItem()
来获取对话框中控件的窗口句柄需要特定对话框实例的窗口句柄。
然后我创建了一组辅助函数来处理无模式对话框的实例。
static struct {
int iId;
HWND hWnd;
} A170DlgTabs[10] = { {0, 0} };
// modeless dialog box message handler which has nothing to do but the
// WinAPI requires it.
BOOL WINAPI A170DlgChildProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
return FALSE;
}
void A170ModeLessChildDialogClear ()
{
memset (A170DlgTabs, 0, sizeof(A170DlgTabs));
}
HWND A170ModeLessChildDialog (HWND hParentWnd, int nCmdShow, int iId)
{
int i;
HWND hWnd = DialogCreation(hResourceDll/*hActInst*/, //RPH 4-23-03 Multilingual
MAKEINTRESOURCEW(IDD_A170_DAYS),
hParentWnd,
A170DlgChildProc);
hWnd && ShowWindow (hWnd, nCmdShow);
for (i = 0; i < sizeof(A170DlgTabs)/sizeof(A170DlgTabs[0]); i++) {
if (A170DlgTabs[i].hWnd == 0) {
A170DlgTabs[i].iId = iId;
A170DlgTabs[i].hWnd = hWnd;
break;
}
}
return hWnd;
}
HWND A170ModeLessChildDialogFind (int iId)
{
int i;
HWND hWnd = NULL;
for (i = 0; i < sizeof(A170DlgTabs)/sizeof(A170DlgTabs[0]); i++) {
if (A170DlgTabs[i].iId == iId) {
hWnd = A170DlgTabs[i].hWnd;
break;
}
}
return hWnd;
}
USHORT A170ModeLessChildDialogSettings (int iId)
{
int i;
USHORT iBits = 0, kBits = 1;
HWND hWnd = A170ModeLessChildDialogFind (iId);
// least significant byte contains the bit mask for the days of the week.
// the next higher byte contains the indicators for the override type or
// whether MDC 298 is to be overriden or not.
for (i = IDD_A170_CAPTION1; i <= IDD_A170_CAPTION7; i++, (kBits <<= 1)) {
iBits |= IsDlgButtonChecked (hWnd, i) ? kBits : 0;
}
iBits |= iBits ? RESTRICT_WEEK_DAYS_ON : 0;
iBits |= IsDlgButtonChecked(hWnd, IDD_A170_STR1) ? KBITS_RESTRICT_OVERRIDE_ANDOR : 0;
iBits |= IsDlgButtonChecked(hWnd, IDD_A170_STR2) ? KBITS_RESTRICT_OVERRIDE_AND : 0;
return iBits;
}
USHORT A170ModeLessChildDialogSettingsSetMask (int iId, USHORT usMask)
{
int i;
USHORT k = 1;
HWND hWnd = A170ModeLessChildDialogFind (iId);
CheckDlgButton(hWnd, IDD_A170_STR1, (usMask & KBITS_RESTRICT_OVERRIDE_ANDOR) ? TRUE : FALSE);
CheckDlgButton(hWnd, IDD_A170_STR2, (usMask & KBITS_RESTRICT_OVERRIDE_AND) ? TRUE : FALSE);
for (i = IDD_A170_CAPTION1; i <= IDD_A170_CAPTION7; i++, (k <<= 1)) {
CheckDlgButton(hWnd, i, (usMask & k) ? TRUE : FALSE);
}
return usMask;
}
使用Visual Studio 2017社区版采用这种方法从对话框模板创建组件,然后用于构建GUI更容易。
我使用“关于”对话框进行了粗略的概念验证练习,该对话框是由创建新的Windows桌面应用程序项目自动生成的,因为它非常方便。
这是一个在对话框模板中表达的简单组件,简单性可能会使这种概念证明误导。
我开始使用作为新项目创建的初始Windows桌面应用程序框架。然后,我对使用New项目自动生成的About对话框进行了以下修改。我能够使用IDE和资源编辑器而无需手动编辑资源文件。
步骤如下:
使用资源编辑器修改对话框资源非常简单:
GetDlgItem()
引用它们,这是一个重要步骤,因为IDE默认情况下不会将可用控件标识符分配给静态窗口源代码更改也相当简单,因为这是一个简单的控件。
新的“关于”对话框如下所示。我将两个静态窗口放大后将其添加到“关联”对话框中。每个静态窗口都有自己的基于新对话框模板的控件实例。请注意,对话框模板的大小大于静态窗口的大小,导致将对话框模板剪切到静态窗口的显示区域。
创建和修改对话框样式
使用资源视图我添加了一个新对话框。我单击了新对话框以在资源编辑器中显示它。然后我通过更改border
属性和style
属性以及删除IDE首次创建对话框模板时添加的默认按钮来修改初始模态对话框模板,我将对话框模板转换为适合放入静态窗口的无模式对话框容器。
为行为创建代码
然后我创建了一个类CDialogChild
来管理这个新的无模式对话框,其中包含以下源代码:
#pragma once
#include "resource.h"
class CDialogChild
{
private:
// static shared by all instances of this class
static LRESULT CALLBACK WndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
enum {DIALOG_ID = IDD_DIALOG1};
static const UINT idCheckBox[3]; // identifiers for the controls of the modeless dialog.
// management data for each modeless dialog instance created.
HINSTANCE m_hinst; // handle to the instance resources
HWND m_hWnd; // handle to the modeless dialog window
HWND m_hParent; // handle to the parent of the modeless dialog, usually static window
// operational data displayed and captured by this modeless dialog
// this is the data for the various controls we have in the dialog template.
bool bCheckBox[3] = { false, false, false };
public:
CDialogChild();
~CDialogChild();
bool GetCheck(int iIndex); // for reading checkbox value
void GetCheckFinal(void); // for capturing final checkbox states
bool SetCheck(int iIndex, bool bValue); // for writing checkbox value
void SetCheckInitial(void); // for setting the initial checkbox states.
HWND Create(HWND hParent, HINSTANCE hinst);
};
执行:
#include "stdafx.h"
#include "CDialogChild.h"
const UINT CDialogChild::idCheckBox[3] = { IDC_CHECK1, IDC_CHECK2, IDC_CHECK3 };
CDialogChild::CDialogChild()
{
}
CDialogChild::~CDialogChild()
{
}
HWND CDialogChild::Create(HWND hParent, HINSTANCE hinst)
{
// called to create the modeless dialog using the dialog resource in the
// specified resource file instance. the hParent is the container we are
// going to put this modeless dialogbox into.
m_hinst = hinst;
m_hParent = hParent;
m_hWnd = CreateDialog(hinst, MAKEINTRESOURCE(DIALOG_ID), hParent, (DLGPROC)CDialogChild::WndProc);
ShowWindow(m_hWnd, SW_SHOW);
return m_hWnd;
}
bool CDialogChild::GetCheck(int iIndex)
{
if (iIndex > 0 && iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0])) {
iIndex = 0;
}
bCheckBox [iIndex] = IsDlgButtonChecked(m_hWnd, idCheckBox[iIndex]);
return bCheckBox [iIndex];
}
bool CDialogChild::SetCheck(int iIndex, bool bValue)
{
if (iIndex > 0 && iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0])) {
iIndex = 0;
}
CheckDlgButton (m_hWnd, idCheckBox[iIndex], bValue);
bCheckBox[iIndex] = bValue;
return bCheckBox [iIndex];
}
void CDialogChild::GetCheckFinal(void)
{
for (int iIndex = 0; iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0]); iIndex++) {
bCheckBox[iIndex] = IsDlgButtonChecked(m_hWnd, idCheckBox[iIndex]);
}
}
void CDialogChild::SetCheckInitial(void)
{
for (int iIndex = 0; iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0]); iIndex++) {
CheckDlgButton(m_hWnd, idCheckBox[iIndex], bCheckBox[iIndex]);
}
}
// CDialogChild class Windows message procedure to handle any messages sent
// to a modeless dialog window. This simple example there is not much to do.
LRESULT CALLBACK CDialogChild::WndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
return (INT_PTR)FALSE;
}
使用对话框中的新控件
最后,我修改了About
对话框,以使用基于对话框模板的新控件。第一件事是将静态窗口添加到“关于”对话框模板,以便为新控件提供容器,该控件为我想要控件实例的位置提供了位置。
接下来,我添加了处理源代码,以便在修改后的About对话框中使用基于对话框模板的新控件。
// other source code from the Windows Desktop Application main window handler
// is above this. We are only modifying the About dialog code which is at the
// bottom of the source file.
#include "CDialogChild.h"
CDialogChild myAbout1;
CDialogChild myAbout2;
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
myAbout1.Create(GetDlgItem(hDlg, IDC_STATIC4), hInst);
myAbout1.SetCheckInitial();
myAbout2.Create(GetDlgItem(hDlg, IDC_STATIC5), hInst);
myAbout2.SetCheckInitial();
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
if (LOWORD(wParam) == IDOK) {
myAbout1.GetCheckFinal();
myAbout2.GetCheckFinal();
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}