Files
minigui-docs/programming-guide-zh/MiniGUIProgGuidePart6Chapter12-zh.md
lisimeng123 12ea4e47e4 update
2022-11-18 00:00:48 +08:00

15 KiB
Raw Permalink Blame History

滚动型控件

滚动型ScrollView控件也是一个滚动窗口控件ScrollWnd 控件的不同之处是滚动型控件中滚动显示的是列表项而不是控件。

滚动型的主要用途是显示和处理列表项,这点和 ListboxListview 控件类似。不过,滚动型中列表项的高度是可以由用户指定的,不同列表项可以有不同的高度。最重要的是,滚动型中列表项的内容绘制完全是由应用程序自己确定的。总的来说,滚动型是一个可定制性很强的控件,给予了应用程序很大的自由,使用滚动型可以完成许多 ListboxListview 控件不能胜任的工作。

1.1 控件风格

具有 SVS_AUTOSORT 风格的滚动型控件将对列表项进行自动排序,前提是已经使用 SVM_SETITEMCMP 消息设置了滚动型控件的列表项比较函数。

SVM_SETITEMCMP myItemCmp;
SendMessage (hScrWnd, SVM_SETITEMCMP, 0, (LPARAM)myItemCmp);

myItemCmp 为应用程序指定的列表项比较函数。

滚动型控件的列表项比较函数是一个 SVM_SETITEMCMP 类型的函数,原型如下:

typedef int (*SVITEM_CMP) (HSVITEM hsvi1, HSVITEM hsvi2);

hsvi1hsvi2 为所要比较的两个列表项的句柄。如果比较函数返回负值,hsvi 代表的列表项将被排序在 hsvi2 代表的列表项之前。

此外,还可以对不具有 SVS_AUTOSORT 风格的滚动型控件使用 SVM_SORTITEMS 消息来对列表项进行一次性的排序。

SVM_SETITEMCMP myItemCmp;
SendMessage (hScrWnd, SVM_SORTITEMS, 0, (LPARAM)myItemCmp);

myItemCmp 为应用程序指定的排序时使用的列表项比较函数。

1.2 滚动型控件消息

除了和 ScrollWnd 控件一样响应一些通用的滚动窗口消息之外,滚动型控件的相关消息主要用于列表项的添加、删除和访问等方面。

1.2.1 列表项的内容显示

滚动型控件的列表项内容显示完全是由应用程序自己确定的,所以,在使用列表项之前,必须首先指定列表项内容的显示方法。SVM_SETITEMDRAW 消息用来设置列表项的绘制函数。

SVITEM_DRAWFUNC myDrawItem;
SendMessage (hScrWnd, SVM_SETITEMDRAW, 0, (LPARAM)myDrawItem);

列表项内容绘制函数是一个 SVITEM_DRAWFUNC 类型的函数,原型如下:

typedef void (*SVITEM_DRAWFUNC) (HWND hWnd, HSVITEM hsvi, HDC hdc, RECT *rcDraw);

传给绘制函数的参数分别为滚动型控件窗口句柄 hWnd、列表项句柄 hsvi、图形设备上下文 hdc 和列表项绘制矩形区域。

列表项绘制函数可以根据滚动型控件的实际用途在指定的矩形区域中绘制自定义的内容,可以是文本,也可以是图片,一切均由应用程序自己决定。

1.2.2 列表项操作函数的设置

SVM_SETITEMOPS 消息可以用来设置列表项相关操作的一些回调函数,包括初始化、绘制和结束函数。

SVITEMOPS myops;
SendMessage (hScrWnd, SVM_SETITEMOPS, 0, (LPARAM)&myops);

myops为一个SVITEMOPS类型的结构指定了滚动型控件针对列表项的相关操作函数。如下

typedef struct _svitem_operations
{
        SVITEM_INITFUNC     initItem;     /** called when an ScrollView item is created */
        SVITEM_DESTROYFUNC  destroyItem;  /** called when an item is destroied */
        SVITEM_DRAWFUNC     drawItem;     /** call this to draw an item */
} SVITEMOPS;

initItem 是创建列表项时调用的初始化函数,原型如下:

typedef int  (*SVITEM_INITFUNC)    (HWND hWnd, HSVITEM hsvi);

传递的参数为控件窗口的句柄hWnd和所创建的列表项的句柄。可以使用该函数在创建列表项时进行一些相关的初始化工作。

destroyItem 是销毁列表项时调用的销毁函数,原型如下:

typedef void (*SVITEM_DESTROYFUNC) (HWND hWnd, HSVITEM hsvi);

传递的参数为控件窗口的句柄 hWnd 和所创建的列表项的句柄。可以使用该函数在销毁列表项时进行一些相关的清理工作,例如释放相关的资源。

drawItem 指定列表项的绘制函数,它的作用和使用 SVM_SETITEMDRAW 消息设置绘制函数是完全一样的。

1.2.3 列表项的操作

SVM_ADDITEM和SVM_DELITEM 消息分别用来添加和删除一个列表项。

int idx;
HSVITEM hsvi;
SVITEMINFO svii;
Idx = SendMessage (hScrWnd, SVM_ADDITEM, (WPARAM)&hsvi, (LPARAM)&svii);

svii 是一个 SVITEMINFO 类型的结构,如下:

typedef struct _SCROLLVIEWITEMINFO
{
        int        nItem;           /** index of item */
        int        nItemHeight;     /** height of an item */
        DWORD      addData;         /** item additional data */
} SVITEMINFO;

nItem 项为列表项的添加位置,如果 nItem 为负值,列表项将被添加到末尾。nItemHeight 为列表项的高度,addData 为列表项的附加数据值。

hsvi 用来存放所添加的列表项的句柄值,该句柄可以用来访问列表项。SVM_ADDITEM 消息返回所添加列表项的实际索引值。

SVM_DELITEM 消息用来删除一个列表项。

int idx;
HSVITEM hsvi;
SendMessage (hScrWnd, SVM_DELITEM, idx, hsvi);

hsvi 指定所要删除的列表项的句柄。如果 hsvi 为 0idx 指定所要删除的列表项的索引值。

SVM_REFRESHITEM 消息用来刷新一个列表项区域。

int idx;
HSVITEM hsvi;
SendMessage (hScrWnd, SVM_REFRESHITEM, idx, hsvi);

hsvi 指定所要刷新的列表项的句柄。如果 hsvi 为 0idx 指定所要刷新的列表项的索引值。

SVM_GETITEMADDDATA 消息用来获取列表项的附加数据。

SendMessage (hScrWnd, SVM_GETITEMADDDATA, idx, hsvi);

hsvi 指定所要访问的列表项的句柄。如果 hsvi 为 0idx 指定所要访问的列表项的索引值。

SVM_SETITEMADDDATA 消息用来设置列表项的附加数据。

int idx;
DWORD addData;
SendMessage (hScrWnd, SVM_SETITEMADDDATA, idx, addData);

idx 指定所要访问的列表项的索引值,addData 为所要设置的附加数据。

SVM_GETITEMCOUNT 消息用来获取当前列表项的数量。

int count = SendMessage (hScrWnd, SVM_GETITEMCOUNT, 0, 0);

SVM_RESETCONTENT 消息用来删除掉控件中所有的列表项。

SendMessage (hScrWnd, SVM_RESETCONTENT, 0, 0);

1.2.4 获取和设置当前高亮项

滚动型控件具有一个高亮列表项属性,也就是说,列表项中仅有(如果有的话)一个列表项是当前高亮的列表项。应用程序可以设置和获取当前高亮的列表项。

需要注意的是:高亮只是滚动型控件的一个属性,某个列表项是当前的高亮项并不代表该列表项在显示上一定有什么特殊之处(如高亮显示),这完全是由应用程序来自己决定的。

SVM_SETCURSEL 消息用来设置控件的高亮列表项。

SendMessage (hScrWnd, SVM_SETCURSEL, idx, bVisible);

idx 指定所要设置为高亮的列表项的索引值,bVisible 如果为 TRUE,该列表项将成为可见项。

SVM_GETCURSEL 消息用来获取控件的当前高亮列表项。

int hilighted_idx = SendMessage (hScrWnd, SVM_GETCURSEL, 0, 0);

SVM_GETCURSEL 消息的返回值为当前高亮列表项的索引值。

1.2.5 列表项的选择和显示

滚动型控件的列表项除了有高亮属性之外,还有选中属性。高亮是唯一的,选中不是唯一的,也就是说,滚动型控件的列表项可以被多选。应用程序可以设置列表项的选中状态。

和高亮属性一样,我们同样要注意:选中只是列表项的一个状态,某个列表项是选中的项并不代表该列表项在显示上一定有什么特殊之处(如高亮显示),这也完全是由应用程序来决定的。

SendMessage (hScrWnd, SVM_SELECTITEM, idx, bSel);

idx 指定所要设置的列表项的索引值。bSel 如果为 TRUE,该列表项将被设置为选中;反之为非选中。

SVM_SHOWITEM 消息用来显示一个列表项。

SendMessage (hScrWnd, SVM_SHOWITEM, idx, hsvi);

hsvi 为所要显示的列表项的句柄。idx 指定所要显示的列表项的索引值,idx 只有在 hsvi 为 0 时起作用。

SVM_CHOOSEITEM 消息是 SVM_SELECTITEMSVM_SHOWITEM 消息的组合,用来选中一个列表项并使之可见。

SendMessage (hScrWnd, SVM_CHOOSEITEM, idx, hsvi);

hsvi 为所要选择和显示的列表项的句柄。idx 指定所要选择和显示的列表项的索引值,idx 只有在 hsvi 为 0 时起作用。

1.2.6 显示的优化

在使用 SVM_ADDITEM 消息或者 SVM_DELITEM 消息一次性增加或者删除很多列表项时,可以使用 MSG_FREEZE 消息进行一定的优化。用法是在操作之前冻结控件,操作之后解冻。

MSG_FREEZE 消息的 wParam 参数如果为 TRUE 则是冻结,反之为解冻。

1.2.7 设置可见区域的范围

滚动型控件的窗口并不全是可视区域还包括边缘margin区域如__图 1.1__ 所示

滚动型的可视区域 图 1.1 滚动型的可视区域

SVM_SETMARGINS 消息可以对滚动型控件的边缘范围进行设置。

RECT rcMargin;
SendMessage (hScrWnd, SVM_SETMARGINS, 0, (LPARAM)&rcMargin);

rcMargin 中的 lefttoprightbottom 项分别为所要设置的左、上、右和下边缘的大小,如果设置的某个边缘值为负值,对应的设置将不起作用。

SVM_GETMARGIN S消息可以获取滚动型控件的边缘范围值。

RECT rcMargin;
SendMessage (hScrWnd, SVM_GETMARGINS, 0, (LPARAM)&rcMargin);

SVM_GETLEFTMARGINSVM_GETTOPMARGINSVM_GETRIGHTMARGINSVM_GETBOTTOMMARGIN 消息分别用来获取左、上、右和下边缘值。

1.3 控件通知码

滚动型控件在响应用户点击等操作和发生某些状态改变时会产生通知消息,包括:

  • SVN_SELCHANGED:当前高亮列表项发生改变
  • SVN_CLICKED:用户点击列表项
  • SVN_SELCHANGING:当前高亮列表项正发生改变

应用程序需要使用 SetNotificationCallback 函数注册一个通知消息处理函数,在该函数中对收到的各个通知码进行应用程序所需的处理。

SVN_CLICKEDSVN_SELCHANGED 通知消息处理函数传递的附加数据为被点击或者当前高亮的列表项句柄。

SVN_SELCHANGING 通知消息处理函数传递的附加数据为先前高亮的列表项句柄。

1.4 编程实例

清单 1.1 中的代码演示了使用滚动型控件来构造一个简单的联系人列表程序的方法。该程序的完整源代码可见本指南示例程序包 mg-samples 中的 scrollview.c 程序。

清单 1.1 滚动型控件示例程序

#define IDC_SCROLLVIEW    100
#define IDC_BT          200
#define IDC_BT2         300
#define IDC_BT3         400
#define IDC_BT4         500

static HWND hScrollView;

static const char *people[] =
{
        "Peter Wang",
        "Michael Li",
        "Eric Liang",
        "Hellen Zhang",
        "Tomas Zhao",
        "William Sun",
        "Alex Zhang"
};

static void myDrawItem (HWND hWnd, HSVITEM hsvi, HDC hdc, RECT *rcDraw)
{
        const char *name = (const char*)ScrollView_get_item_adddata (hsvi);
        
        SetBkMode (hdc, BM_TRANSPARENT);
        SetTextColor (hdc, PIXEL_black);
        
        if (ScrollView_is_item_hilight(hWnd, hsvi)) {
                SetBrushColor (hdc, PIXEL_blue);
                FillBox (hdc, rcDraw->left+1, rcDraw->top+1, RECTWP(rcDraw)-2, RECTHP(rcDraw)-1);
                SetBkColor (hdc, PIXEL_blue);
                SetTextColor (hdc, PIXEL_lightwhite);
        }
        
        Rectangle (hdc, rcDraw->left, rcDraw->top, rcDraw->right - 1, rcDraw->bottom);
        TextOut (hdc, rcDraw->left + 3, rcDraw->top + 2, name);
}

static int myCmpItem (HSVITEM hsvi1, HSVITEM hsvi2)
{
        const char *name1 = (const char*)ScrollView_get_item_adddata (hsvi1);
        const char *name2 = (const char*)ScrollView_get_item_adddata (hsvi2);
        
        return strcmp (name1, name2);
}

static int
BookProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam)
{
        
        switch (message)
        {
                
                case MSG_INITDIALOG:
                {
                        SVITEMINFO svii;
                        static int i = 0;
                        
                        hScrollView = GetDlgItem (hDlg, IDC_SCROLLVIEW);
                        SetWindowBkColor (hScrollView, PIXEL_lightwhite);
                        
                        SendMessage (hScrollView, SVM_SETITEMCMP, 0, (LPARAM)myCmpItem);
                        SendMessage (hScrollView, SVM_SETITEMDRAW, 0, (LPARAM)myDrawItem);
                        
                        for (i = 0; i < TABLESIZE(people); i++) {
                                svii.nItemHeight = 32;
                                svii.addData = (DWORD)people[i];
                                svii.nItem = i;
                                SendMessage (hScrollView, SVM_ADDITEM, 0, (LPARAM)&svii);
                        }
                        break;
                }
                
                case MSG_COMMAND:
                {
                        int id = LOWORD (wParam);
                        int code = HIWORD (wParam);
                        
                        switch (id) {
                                case IDC_SCROLLVIEW:
                                if (code == SVN_CLICKED) {
                                        int sel;
                                        sel = SendMessage (hScrollView, SVM_GETCURSEL, 0, 0);
                                        InvalidateRect (hScrollView, NULL, TRUE);
                                }
                                break;
                                
                        }
                        break;
                }
                
                case MSG_CLOSE:
                {
                        EndDialog (hDlg, 0);
                        return 0;
                }
                
        }
        
        return DefaultDialogProc (hDlg, message, wParam, lParam);
}

static CTRLDATA CtrlBook[] =
{
        {
                "ScrollView",
                WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |
                SVS_AUTOSORT,
                10, 10, 320, 150,
                IDC_SCROLLVIEW,
                "",
                0
        },
};

static DLGTEMPLATE DlgBook =
{
        WS_BORDER | WS_CAPTION,
        WS_EX_NONE,
        0, 0, 350, 200,
        "My Friends",
        0, 0,
        TABLESIZE(CtrlBook), NULL,
        0
};

该程序把联系人以列表的形式显示出来,并且按名字进行了排序。

联系人列表 图 1.2 联系人列表