Windows对话框和滚动条
cheungmine 2010-1
尽管在Windows平台上编程多年,对滚动条的理解还是很肤浅。尤其给对话框添加滚动条。参考下面的文章:
http://www.codeproject.com/KB/dialog/scroll_dialog.aspx
By Alex Blekhman
可以给纯Win32 SDK的程序极为方便地添加滚动条:水平或垂直。
我的目的是给WTL写的程序添加滚动条。我以WTL开发Windows Mobile 6程序为例,说明如何给这样的对话框添加滚动条。
首先是用VS2005+WTL8.0创建一个WTL Mobile Application Wizard项目,WceDlg。
Platforms选择:Windows Mobile 6 Professional SDK。
Application Type:勾选Dialog Based。
其余默认,按Finish。
我仅修改系统自动生成的对话框类文件:WceDlgDialog.h
// WceDlgDialog.h : interface of the CWceDlgDialog class
// cheungmine 2010
/////////////////////////////////////////////////////////////////////////////
#pragma once
////////////////////////////////////////////////////////////////
// From:
// http://www.codeproject.com/KB/dialog/scroll_dialog.aspx
// Scrollable Dialog in Pure Win32 API
// By Alex Blekhman
//
static BOOL SD_OnInitDialog(HWND hwnd, HWND /*hwndFocus*/, LPARAM /*lParam*/)
{
RECT rc = {};
GetClientRect(hwnd, &rc);
const SIZE sz = { rc.right - rc.left, rc.bottom - rc.top };
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
si.nPos = si.nMin = 1;
si.nMax = sz.cx;
si.nPage = sz.cx;
SetScrollInfo(hwnd, SB_HORZ, &si, FALSE);
si.nMax = sz.cy;
si.nPage = sz.cy;
SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
return FALSE;
}
static void SD_OnCommand(HWND hwnd, int id, HWND /*hwndCtl*/, UINT /*codeNotify*/)
{
switch(id)
{
case IDOK:
case IDCANCEL:
EndDialog(hwnd, 0);
break;
}
}
static void SD_ScrollClient(HWND hwnd, int bar, int pos)
{
static int s_prevx = 1;
static int s_prevy = 1;
int cx = 0;
int cy = 0;
int& delta = (bar == SB_HORZ ? cx : cy);
int& prev = (bar == SB_HORZ ? s_prevx : s_prevy);
delta = prev - pos;
prev = pos;
if(cx || cy)
{
::ScrollWindowEx(hwnd, cx, cy, NULL, NULL, 0, 0, SW_SCROLLCHILDREN);
}
}
static void SD_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
if(state != SIZE_RESTORED && state != SIZE_MAXIMIZED)
return;
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
const int bar[] = { SB_HORZ, SB_VERT };
const int page[] = { cx, cy };
for(size_t i = 0; i < sizeof(bar)/sizeof(bar[0]); ++i)
{
si.fMask = SIF_PAGE;
si.nPage = page[i];
SetScrollInfo(hwnd, bar[i], &si, TRUE);
si.fMask = SIF_RANGE | SIF_POS;
GetScrollInfo(hwnd, bar[i], &si);
const int maxScrollPos = si.nMax - (page[i] - 1);
// Scroll client only if scroll bar is visible and window's
// content is fully scrolled toward right and/or bottom side.
// Also, update window's content on maximize.
const bool needToScroll =
(si.nPos != si.nMin && si.nPos == maxScrollPos) ||
(state == SIZE_MAXIMIZED);
if(needToScroll)
{
SD_ScrollClient(hwnd, bar[i], si.nPos);
}
}
}
static int SD_GetScrollPos(HWND hwnd, int bar, UINT code)
{
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
GetScrollInfo(hwnd, bar, &si);
const int minPos = si.nMin;
const int maxPos = si.nMax - (si.nPage - 1);
int result = -1;
switch(code)
{
case SB_LINEUP /*SB_LINELEFT*/:
result = max(si.nPos - 1, minPos);
break;
case SB_LINEDOWN /*SB_LINERIGHT*/:
result = min(si.nPos + 1, maxPos);
break;
case SB_PAGEUP /*SB_PAGELEFT*/:
result = max(si.nPos - (int)si.nPage, minPos);
break;
case SB_PAGEDOWN /*SB_PAGERIGHT*/:
result = min(si.nPos + (int)si.nPage, maxPos);
break;
case SB_THUMBPOSITION:
// do nothing
break;
case SB_THUMBTRACK:
result = si.nTrackPos;
break;
case SB_TOP /*SB_LEFT*/:
result = minPos;
break;
case SB_BOTTOM /*SB_RIGHT*/:
result = maxPos;
break;
case SB_ENDSCROLL:
// do nothing
break;
}
return result;
}
static void SD_OnHVScroll(HWND hwnd, int bar, UINT code)
{
const int scrollPos = SD_GetScrollPos(hwnd, bar, code);
if(scrollPos == -1)
return;
SetScrollPos(hwnd, bar, scrollPos, TRUE);
SD_ScrollClient(hwnd, bar, scrollPos);
}
static void SD_OnHScroll(HWND hwnd, HWND /*hwndCtl*/, UINT code, int /*pos*/)
{
SD_OnHVScroll(hwnd, SB_HORZ, code);
}
static void SD_OnVScroll(HWND hwnd, HWND /*hwndCtl*/, UINT code, int /*pos*/)
{
SD_OnHVScroll(hwnd, SB_VERT, code);
}
////////////////////////////////////////////////////////////////
class CWceDlgDialog :
public CAppStdOrientedDialogImpl<CWceDlgDialog>,
public CUpdateUI<CWceDlgDialog>,
public CMessageFilter, public CIdleHandler
{
public:
DECLARE_APP_DLG_CLASS(NULL, IDR_MAINFRAME, L"Software//WTL//WceDlg")
// 资源中,把下面的对话框拉的大大的,上面放一些Button,Edit之类的控件。
// 并设置对话框垂直和水平滚动条为true
enum { IDD = IDD_MAINDLG };
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
return CWindow::IsDialogMessage(pMsg);
}
// CAppWindow operations
bool AppHibernate( bool bHibernate)
{
// Insert your code here or delete member if not relevant
return bHibernate;
}
bool AppNewInstance( LPCTSTR lpstrCmdLine)
{
// Insert your code here or delete member if not relevant
return false;
}
void AppSave()
{
CAppInfo info;
// Insert your code here or delete member if not relevant
}
virtual BOOL OnIdle()
{
return FALSE;
}
BEGIN_UPDATE_UI_MAP(CWceDlgDialog)
END_UPDATE_UI_MAP()
BEGIN_MSG_MAP(CWceDlgDialog)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
// add by cheungmine
MESSAGE_HANDLER(WM_COMMAND, OnCommand)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_HSCROLL, OnHScroll)
MESSAGE_HANDLER(WM_VSCROLL, OnVScroll)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
CHAIN_MSG_MAP(CAppStdOrientedDialogImpl<CWceDlgDialog>)
END_MSG_MAP()
// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HWND hMenuBar = CreateMenuBar(ATL_IDM_MENU_DONECANCEL);
UIAddToolBar(hMenuBar);
UIAddChildWindowContainer(m_hWnd);
// add by cheungmine
SD_OnInitDialog(m_hWnd, (HWND)(wParam), lParam);
// register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
return bHandled = FALSE;
}
// add by cheungmine
LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SD_OnCommand(m_hWnd, (int)(LOWORD(wParam)), (HWND)(lParam), (UINT)HIWORD(wParam));
// 交由系统继续处理
return bHandled = FALSE;
}
// add by cheungmine
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SD_OnSize(m_hWnd, (UINT)(wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
// 交由系统继续处理
return bHandled = FALSE;
}
// add by cheungmine
LRESULT OnHScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SD_OnHScroll(m_hWnd, (HWND)(lParam), (UINT)(LOWORD(wParam)), (int)(short)HIWORD(wParam));
return 0;
}
// add by cheungmine
LRESULT OnVScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SD_OnVScroll(m_hWnd, (HWND)(lParam), (UINT)(LOWORD(wParam)), (int)(short)HIWORD(wParam));
return 0;
}
LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}
};
编译并部署到设备,可以看到对话框正确地设置了滚动条。再次感谢 Alex Blekhman.
下面是 Alex Blekhman的原代码 :
////////////////////////////////////////////////////////////////////////////////
//
#include "StdAfx.h"
INT_PTR CALLBACK SD_DialogProc(
HWND hwndDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
////////////////////////////////////////////////////////////////////////////////
//
int WINAPI _tWinMain(
HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/,
LPWSTR /*lpCmdLine*/,
int /*nShowCmd*/)
{
DialogBox(
hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
GetDesktopWindow(),
SD_DialogProc);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
//
BOOL SD_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
void SD_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
void SD_OnSize(HWND hwnd, UINT state, int cx, int cy);
void SD_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos);
void SD_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos);
void SD_OnHVScroll(HWND hwnd, int bar, UINT code);
void SD_ScrollClient(HWND hwnd, int bar, int pos);
int SD_GetScrollPos(HWND hwnd, int bar, UINT code);
INT_PTR CALLBACK SD_DialogProc(
HWND hwndDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
switch(uMsg)
{
HANDLE_MSG(hwndDlg, WM_INITDIALOG, SD_OnInitDialog);
HANDLE_MSG(hwndDlg, WM_COMMAND, SD_OnCommand);
HANDLE_MSG(hwndDlg, WM_SIZE, SD_OnSize);
HANDLE_MSG(hwndDlg, WM_HSCROLL, SD_OnHScroll);
HANDLE_MSG(hwndDlg, WM_VSCROLL, SD_OnVScroll);
}
return FALSE;
}
BOOL SD_OnInitDialog(HWND hwnd, HWND /*hwndFocus*/, LPARAM /*lParam*/)
{
RECT rc = {};
GetClientRect(hwnd, &rc);
const SIZE sz = { rc.right - rc.left, rc.bottom - rc.top };
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
si.nPos = si.nMin = 1;
si.nMax = sz.cx;
si.nPage = sz.cx;
SetScrollInfo(hwnd, SB_HORZ, &si, FALSE);
si.nMax = sz.cy;
si.nPage = sz.cy;
SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
return FALSE;
}
void SD_OnCommand(HWND hwnd, int id, HWND /*hwndCtl*/, UINT /*codeNotify*/)
{
switch(id)
{
case IDOK:
case IDCANCEL:
EndDialog(hwnd, 0);
break;
}
}
void SD_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
if(state != SIZE_RESTORED && state != SIZE_MAXIMIZED)
return;
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
const int bar[] = { SB_HORZ, SB_VERT };
const int page[] = { cx, cy };
for(size_t i = 0; i < ARRAYSIZE(bar); ++i)
{
si.fMask = SIF_PAGE;
si.nPage = page[i];
SetScrollInfo(hwnd, bar[i], &si, TRUE);
si.fMask = SIF_RANGE | SIF_POS;
GetScrollInfo(hwnd, bar[i], &si);
const int maxScrollPos = si.nMax - (page[i] - 1);
// Scroll client only if scroll bar is visible and window's
// content is fully scrolled toward right and/or bottom side.
// Also, update window's content on maximize.
const bool needToScroll =
(si.nPos != si.nMin && si.nPos == maxScrollPos) ||
(state == SIZE_MAXIMIZED);
if(needToScroll)
{
SD_ScrollClient(hwnd, bar[i], si.nPos);
}
}
}
void SD_OnHScroll(HWND hwnd, HWND /*hwndCtl*/, UINT code, int /*pos*/)
{
SD_OnHVScroll(hwnd, SB_HORZ, code);
}
void SD_OnVScroll(HWND hwnd, HWND /*hwndCtl*/, UINT code, int /*pos*/)
{
SD_OnHVScroll(hwnd, SB_VERT, code);
}
void SD_OnHVScroll(HWND hwnd, int bar, UINT code)
{
const int scrollPos = SD_GetScrollPos(hwnd, bar, code);
if(scrollPos == -1)
return;
SetScrollPos(hwnd, bar, scrollPos, TRUE);
SD_ScrollClient(hwnd, bar, scrollPos);
}
void SD_ScrollClient(HWND hwnd, int bar, int pos)
{
static int s_prevx = 1;
static int s_prevy = 1;
int cx = 0;
int cy = 0;
int& delta = (bar == SB_HORZ ? cx : cy);
int& prev = (bar == SB_HORZ ? s_prevx : s_prevy);
delta = prev - pos;
prev = pos;
if(cx || cy)
{
ScrollWindowEx(hwnd, cx, cy, NULL, NULL, 0, 0, 0);
}
}
int SD_GetScrollPos(HWND hwnd, int bar, UINT code)
{
SCROLLINFO si = {};
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
GetScrollInfo(hwnd, bar, &si);
const int minPos = si.nMin;
const int maxPos = si.nMax - (si.nPage - 1);
int result = -1;
switch(code)
{
case SB_LINEUP /*SB_LINELEFT*/:
result = max(si.nPos - 1, minPos);
break;
case SB_LINEDOWN /*SB_LINERIGHT*/:
result = min(si.nPos + 1, maxPos);
break;
case SB_PAGEUP /*SB_PAGELEFT*/:
result = max(si.nPos - (int)si.nPage, minPos);
break;
case SB_PAGEDOWN /*SB_PAGERIGHT*/:
result = min(si.nPos + (int)si.nPage, maxPos);
break;
case SB_THUMBPOSITION:
// do nothing
break;
case SB_THUMBTRACK:
result = si.nTrackPos;
break;
case SB_TOP /*SB_LEFT*/:
result = minPos;
break;
case SB_BOTTOM /*SB_RIGHT*/:
result = maxPos;
break;
case SB_ENDSCROLL:
// do nothing
break;
}
return result;
}