65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 Windows Mobile 的基于列表的窗体

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (9投票s)

2007 年 10 月 18 日

CPOL

9分钟阅读

viewsIcon

46835

downloadIcon

417

实现了一个类似 Pocket Outlook 的列表式表单,并增加了一些额外功能。

Screenshot - FormListSample.png

引言

本文介绍了一种 MFC 实现,用于在 Windows Mobile Pocket Outlook 应用程序中找到的基于列表的数据输入表单。

背景

在 Windows Mobile 设备上进行数据输入对于用户和开发人员来说都是一个痛点。用户要求输入清晰且易于访问,而开发人员则要求输入代码易于开发和维护。我第一次遇到这个挑战是在 2002-2003 年,当时为 Pocket PC 2002 开发数据库应用程序意味着使用 C++ 和 MFC 以及现已停用的 ADOCE 3.1 进行数据库访问。应用程序数据输入主要通过对话框实现,设计工具是有限的 eVC3 / eVC4 对话框编辑器。使用 MFC 的对话框数据交换在对话框开发方面有所帮助,但这需要两步:开发对话框模板,然后添加控件代码。当维护周期开始时,情况会变得棘手,特别是当您需要在已经拥挤的对话框中添加一个额外的字段时。

当时,我发现了两种方法可以为对话框腾出更多空间:要么将其转换为带有多个页面的属性表,要么实现滚动条(当时外壳尚不支持)。属性表方法不太适合扩展对话框,因为您需要找到一种逻辑方式将对话框分成页面,而最终结果有时并不直观。此外,它要求用户翻阅有时众多的页面才能找到他/她想要的数据。我采用的可滚动对话框的方法并不令人信服,因为滚动时会出现明显的屏幕闪烁。此外,这种方法要求所有对话框子控件同时创建,这对于保留所有托管窗口句柄的 MFC 应用程序来说是一个减慢因素。

有趣的是,微软通过在 Pocket Outlook 应用程序中使用基于列表的表单控件解决了数据输入问题。这种控件最大的优点是直观易用,但最大的缺点是没有公开的 API。经过一次不太成功的尝试后,我编写了一组类,在这里我介绍了一个用于实现 CFormListCtrl MFC 控件的类集。这段代码最初是在 2004 年在 eVC3 编译器上开发的,并为此文章改编为 Visual Studio 2005。我编写的改编内容包括屏幕分辨率和方向感知、键盘支持和项目选择模式。

实现的 UI

控件的用户界面按行组织,每行包含一个项目。行项目有两种类型:组和数据项。组项目由一个树状的加号/减号按钮标识,该按钮用于展开和折叠所有包含的项目。请注意,此实现不支持递归组(一个组不能包含另一个组)。另一种行项目是数据项。每个数据项包含一个文本标签和一个数据区域,该区域将显示要编辑的数据以及用于编辑该数据控件。字符串数据项在编辑模式下显示数据区域中的字符串值和编辑控件。

任何时候只能有一个项目处于编辑模式。当用户单击项目或使用键盘(向上和向下箭头)选择项目并按下 Enter 键时,项目将进入编辑模式。退出编辑模式取决于显示的编辑控件。在示例代码中,所有数字值都通过一个类似计算器的对话框进行编辑,在这些项目上退出编辑模式要求用户单击“确定”或“取消”按钮。另一方面,字符串项目通过用户使用向上或向下导航键移出项目来退出编辑模式。

组项目的使用方式与普通编辑项目相同。用户可以使用向上和向下箭头键导航到组项目,并且可以通过按 Enter 键来展开或折叠组。单击组项目具有相同效果。

数据项的标题可以通过键盘或触控笔进行调整大小。左键和右键导航键将使标题大小每次按键向左或向右调整两个像素。用户还可以通过单击标题和数据区域之间的区域来调整标题大小。当显示垂直灰线时,用户可以左右拖动它来设置新的标题宽度。

数据项可以具有“选项”设置,通常实现为标题上的上下文菜单。带有选项的项目在图形上通过标题文本左侧的向下箭头来标识。用户通过单击标题来激活此功能。

使用代码

我的代码设计模式非常简单。首先,您必须确定列表控件的宿主位置。它可以是对话框或属性表。您可以通过对话框编辑器创建容器,或者使用附带的 CDialogTemplate 类动态创建内存中的对话框模板,因此您可以将此代码用作组件,而无需任何资源。使用此技术时,请确保使用以下样式创建列表控件模板:LVS_REPORT | LVS_SINGLESEL | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | WS_TABSTOP

在创建第一个表单之前,需要简要描述相关类

描述
CBaseListCtrl 表单列表控件的基类。处理 MFC 的一些特殊情况。
CFormListCtrl 表单列表控件。从该类派生您的表单。
CFormItem 所有数据项的基类。将其用作自定义数据项的基类。
CFormItemGroup CFormItem 对象分组到可折叠的容器中。
CFormItemCheck 显示一个复选框。
CFormItemCombo 显示一个组合框,用于从一组字符串中进行选择。
CFormItemDateTime 显示一个日期或时间选择器控件。允许编辑日期或时间,但不能同时编辑。
CFormItemNumber 数字数据项,支持整数、通用数字和货币格式。值使用类似计算器的对话框进行编辑。
CFormItemString 通用字符串编辑器。

CFormListCtrl 类通过 CBaseListCtrl 从 MFC 的 CListCtrl 派生。表单中的每个项目都是一个 CFormItem 派生类。基类实现了通用的自定义绘制逻辑,将专门的自定义绘制实现和输入控件的管理留给派生类。

创建专用表单

创建新表单的第一步是派生一个新类 CFormListCtrl。这将是您的专用表单,所有数据项都将在其中声明。

class CDemoForm : public CFormListCtrl
{
public:
    CDemoForm(void);
    virtual ~CDemoForm(void);

    CFormItemGroup    m_groupUser,
                      m_groupData;
    CFormItemString   m_itemFirstName,
                      m_itemLastName;
    CFormItemDateTime m_itemBirthDate;
    CFormItemNumber   m_itemInteger,
                      m_itemNumber,
                      m_itemCurrency;
    CFormItemCombo    m_itemCombo;
    CFormItemCheck    m_itemCheck;

    virtual BOOL Initialize();
    virtual void ItemOptions(CFormItem* pItem);

private:
    CMenu m_menuCombo; // Menu for combo box item

};

初始化项目

每个项目都是静态的,因此您无需担心内存管理。声明这些对象是第一步;创建表单的下一步是在表单构造函数中初始化这些对象。

m_groupUser.Init(_T("User"));
m_groupData.Init(_T("Data"));
    
m_itemLastName .Init(_T("Last name:"), FIF_NORMAL | FIF_MANDATORY);
m_itemFirstName.Init(_T("First name:"));
m_itemBirthDate.Init(_T("Birth date:"));
m_itemCheck    .Init(_T("Check:"));

m_itemInteger .Init(_T("An integer:"), fmtInteger);
m_itemNumber  .Init(_T("A number:"),   fmtNumber);
m_itemCurrency.Init(_T("Currency:"),   fmtCurrency);

m_itemCombo.Init(_T("Selection:"), FIF_NORMAL | FIF_HASOPTIONS);
m_itemCombo.AddString(_T("Apples"),  1);
m_itemCombo.AddString(_T("Oranges"), 2);
m_itemCombo.AddString(_T("Grapes"),  3);
m_itemCombo.SetSel(0);

项目初始化包括设置标题字符串和其他一些可选参数。根据项目类型,Init 参数列表会有所不同,但所有项目中最重要且通用的参数之一是标志位掩码。

#define FIF_LINE        0x00000001    // Draw a line at the bottom

#define FIF_VISIBLE     0x00000002    // Item is visible

#define FIF_ENABLED     0x00000004    // Item is enabled

#define FIF_MANDATORY   0x00000008    // Data for the item must be filled in by the user

#define FIF_UNDERLINE   0x00000010    // Underline the caption text

#define FIF_HASOPTIONS  0x00000020    // The item has a context menu (on the caption)

#define FIF_DRAWOPTIONS 0x00000040    // Internal flag

#define FIF_SELECTED    0x00000080    // The item is selected

#define FIF_NORMAL      (FIF_LINE|FIF_VISIBLE|FIF_ENABLED)

默认情况下,所有项目都使用 FIF_NORMAL 标志创建。在此示例中,我在第一个和最后一个数据项上使用了两个特殊标志:FIF_MANDATORYFIF_HASOPTIONS。第一个标志告诉项目在表单关闭之前必须填写数据。图形上,该项目将显示为红色标题和黄色数据背景。FIF_HASOPTIONS 标志将该项目标记为显示自定义选项,这些选项由 ItemOptions 虚拟方法处理。

在显示表单之前,您可以设置项目的初始值。每个项目都有一组 GetSet 函数,用于将数据传输到项目和从项目传输数据。请注意,项目数据是缓存的,因此在表单显示后设置数据需要重新绘制项目。您可以通过常规调用 CFormListCtrl::RedrawItems 来完成此操作,该函数从 MFC 的 CListCtrl 继承。

表单初始化

表单初始化由虚拟 Initialize 方法处理。此方法从基类继承,其中发生了一些初始化(例如创建默认字体并添加两个不可见的头部列)。您的代码必须在执行自己的操作之前调用基类初始化方法。

BOOL CDemoForm::Initialize()
{
    if(CFormListCtrl::Initialize())
    {
        EnableEdit(TRUE);

        m_groupUser.AddItem(&m_itemLastName);
        m_groupUser.AddItem(&m_itemFirstName);
        m_groupUser.AddItem(&m_itemBirthDate);
        m_groupUser.AddItem(&m_itemCheck);

        m_groupData.AddItem(&m_itemInteger);
        m_groupData.AddItem(&m_itemNumber);
        m_groupData.AddItem(&m_itemCurrency);
        m_groupData.AddItem(&m_itemCombo);

        AddItem(&m_groupUser);
        AddItem(&m_groupData);

        return TRUE;
    }
    return FALSE;
}

第一行将此表单标记为可编辑,而其他行则通过设置组中的数据项来创建表单结构。请注意,只有组项目被添加到表单中。这不是强制性的——您可以直接将数据项添加到表单中,而不仅仅是添加到组项目中。您不能嵌套组。

项目选项

项目选项允许您为项目添加额外功能。如上所示,您必须用标志标记一个带选项的项目,当用户单击其标题时,您的表单的 ItemOptions 虚拟方法将被调用,并传入一个指向该项目的指针。这是此示例实现此方法的方式。

void CDemoForm::ItemOptions(CFormItem *pItem)
{
    // Check if this is the combo box item

    if(pItem == &m_itemCombo)
    {
        int iItem;

        // Get the item index

        iItem = FindFormItem(pItem);
        if(iItem != -1)
        {
            CRect    rc;

            // Now get the item bounding rect

            if(GetItemRect(iItem, &rc, LVIR_BOUNDS))
            {
                CPoint pt(rc.left, rc.bottom);
                int    nCmd;

                // Display a popup menu below the arrow

                ClientToScreen(&pt);
                nCmd = m_menuCombo.TrackPopupMenu(TPM_LEFTALIGN | 
                                TPM_RETURNCMD, pt.x, pt.y, this);

                switch(nCmd)
                {
                case ID_COMBO_ADD:
                    MessageBox(_T("Add new item"), _T("Option"));
                    break;
                case ID_COMBO_EDIT:
                    MessageBox(_T("Edit item"), _T("Option"));
                    break;
                case ID_COMBO_DELETE:
                    MessageBox(_T("Delete item"), _T("Option"));
                    break;
                }
            }
        }
    }
}

在这里,我只是在选项箭头下方显示一个上下文菜单,但您可以实现任何您想要的功能,例如显示另一个对话框。如果您想显示像此示例一样的上下文菜单,请重用上面的代码,因为它正确地将上下文菜单定位在项目标题下方。

关注点

我在此处提供的代码有些陈旧(2003-2004 年左右),并且需要进行一些改编才能适应现代 Windows Mobile 设备。编写此代码时,Windows Mobile 设备只有一个显示分辨率且没有键盘。现在,您拥有各种带或不带键盘或轨迹球的设备,并且显示分辨率和屏幕方向各不相同。

有趣的是,使用微软自己的“设备分辨率感知”头文件(DeviceResolutionAware.h)可以轻松改编代码。这个文件包含一些值得研究的非常有趣的编程技巧。

致谢

本文中的部分代码改编或启发自以下来源:

我还使用了我之前发布的代码的改编版本。

历史

  • 2007-10-19:代码已更新,以处理 WM 2003 的一些细微的行为差异。
  • 2007-10-18:已发布。
© . All rights reserved.