[win32] RP_ShellPropSheetExt: Initial scrolling for data widgets.

Currently only works if multiple tabs are displayed.

The child dialog control has WS_VSCROLL and the scroll information is
set after all the tabs are set up.

SubtabDlgProc:
- WM_VSCROLL: Scroll the window as necessary, using the description
  label height as the line height and one visible page worth as the
  page height.
- Call DefSubclassProc() instead of simply returning FALSE.

This partially fixes issue #204: Windows UI has no scroll bar
Reported by @InternalLoss.
This commit is contained in:
David Korth 2020-09-15 20:06:53 -04:00
parent 43f27f0964
commit cff90b5309
3 changed files with 167 additions and 50 deletions

View File

@ -58,7 +58,7 @@
#define NOMINMAX 1 #define NOMINMAX 1
//#define NOMSG 1 //#define NOMSG 1
#define NOOPENFILE 1 #define NOOPENFILE 1
#define NOSCROLL 1 //#define NOSCROLL 1
#define NOSERVICE 1 #define NOSERVICE 1
//#define NOSOUND 1 //#define NOSOUND 1
//#define NOTEXTMETRIC 1 //#define NOTEXTMETRIC 1

View File

@ -106,9 +106,13 @@ class RP_ShellPropSheetExt_Private
public: public:
// Property for "D pointer". // Property for "D pointer".
// This points to the ConfigDialogPrivate object. // This points to the RP_ShellPropSheetExt_Private object.
static const TCHAR D_PTR_PROP[]; static const TCHAR D_PTR_PROP[];
// Property for "tab pointer".
// This points to the RP_ShellPropSheetExt_Private::tab object.
static const TCHAR TAB_PTR_PROP[];
public: public:
// ROM filename. // ROM filename.
string filename; string filename;
@ -198,14 +202,19 @@ class RP_ShellPropSheetExt_Private
HWND hDlg; // Tab child dialog. HWND hDlg; // Tab child dialog.
HWND lblCredits; // Credits label. HWND lblCredits; // Credits label.
POINT curPt; // Current point. POINT curPt; // Current point.
int scrollPos; // Scrolling position.
tab() : hDlg(nullptr), lblCredits(nullptr) { tab() : hDlg(nullptr), lblCredits(nullptr), scrollPos(0) {
curPt.x = 0; curPt.y = 0; curPt.x = 0; curPt.y = 0;
} }
}; };
vector<tab> tabs; vector<tab> tabs;
int curTabIndex; int curTabIndex;
// Sizes.
int lblDescHeight; // Description label height.
SIZE dlgSize; // Visible dialog size.
// MessageWidget for ROM operation notifications. // MessageWidget for ROM operation notifications.
HWND hMessageWidget; HWND hMessageWidget;
int iTabHeightOrig; int iTabHeightOrig;
@ -252,6 +261,21 @@ class RP_ShellPropSheetExt_Private
void loadImages(void); void loadImages(void);
private: private:
/**
* Check if we need to use WS_EX_LAYOUTRTL.
* TODO: Cache this value?
* @return WS_EX_LAYOUTRTL if process is RTL; otherwise 0.
*/
static inline DWORD checkLayoutRTL(void)
{
// Set WS_EX_LAYOUTRTL if the process is RTL.
DWORD dwDefaultLayout;
const BOOL bRet = GetProcessDefaultLayout(&dwDefaultLayout);
return (unlikely(bRet && dwDefaultLayout == LAYOUT_RTL))
? WS_EX_LAYOUTRTL
: 0;
}
/** /**
* Rescale an image to be as close to the required size as possible. * Rescale an image to be as close to the required size as possible.
* @param req_sz [in] Required size. * @param req_sz [in] Required size.
@ -446,7 +470,7 @@ class RP_ShellPropSheetExt_Private
* @param uIdSubclass * @param uIdSubclass
* @param dWRefData RP_ShellPropSheetExt_Private * @param dWRefData RP_ShellPropSheetExt_Private
*/ */
static LRESULT CALLBACK DialogSubclassProc( static LRESULT CALLBACK MainDialogSubclassProc(
HWND hWnd, UINT uMsg, HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData); UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
@ -484,6 +508,10 @@ class RP_ShellPropSheetExt_Private
// This points to the ConfigDialogPrivate object. // This points to the ConfigDialogPrivate object.
const TCHAR RP_ShellPropSheetExt_Private::D_PTR_PROP[] = _T("RP_ShellPropSheetExt_Private"); const TCHAR RP_ShellPropSheetExt_Private::D_PTR_PROP[] = _T("RP_ShellPropSheetExt_Private");
// Property for "tab pointer".
// This points to the RP_ShellPropSheetExt_Private::tab object.
const TCHAR RP_ShellPropSheetExt_Private::TAB_PTR_PROP[] = _T("RP_ShellPropSheetExt_Private::tab");
RP_ShellPropSheetExt_Private::RP_ShellPropSheetExt_Private(RP_ShellPropSheetExt *q, string &&filename) RP_ShellPropSheetExt_Private::RP_ShellPropSheetExt_Private(RP_ShellPropSheetExt *q, string &&filename)
: q_ptr(q) : q_ptr(q)
, filename(std::move(filename)) , filename(std::move(filename))
@ -502,6 +530,7 @@ RP_ShellPropSheetExt_Private::RP_ShellPropSheetExt_Private(RP_ShellPropSheetExt
, dwExStyleRTL(0) , dwExStyleRTL(0)
, tabWidget(nullptr) , tabWidget(nullptr)
, curTabIndex(0) , curTabIndex(0)
, lblDescHeight(0)
, hMessageWidget(nullptr) , hMessageWidget(nullptr)
, iTabHeightOrig(0) , iTabHeightOrig(0)
, def_lc(0) , def_lc(0)
@ -511,6 +540,10 @@ RP_ShellPropSheetExt_Private::RP_ShellPropSheetExt_Private(RP_ShellPropSheetExt
// Initialize the alternate row color. // Initialize the alternate row color.
colorAltRow = LibWin32Common::getAltRowColor(); colorAltRow = LibWin32Common::getAltRowColor();
// Initialize other structs.
dlgSize.cx = 0;
dlgSize.cy = 0;
// Check for RTL. // Check for RTL.
// NOTE: Windows Explorer on Windows 7 seems to return 0 from GetProcessDefaultLayout(), // NOTE: Windows Explorer on Windows 7 seems to return 0 from GetProcessDefaultLayout(),
// even if an RTL language is in use. We'll check the taskbar layout instead. // even if an RTL language is in use. We'll check the taskbar layout instead.
@ -1131,6 +1164,9 @@ int RP_ShellPropSheetExt_Private::initBitfield(HWND hDlg, HWND hWndTab,
} }
pt.y -= rect_subtract.bottom; pt.y -= rect_subtract.bottom;
// WS_EX_LAYOUTRTL
const DWORD dwExStyleRTL = checkLayoutRTL();
row = 0; col = 0; row = 0; col = 0;
auto iter = tnames.cbegin(); auto iter = tnames.cbegin();
uint32_t bitfield = field.data.bitfield; uint32_t bitfield = field.data.bitfield;
@ -2580,7 +2616,8 @@ void RP_ShellPropSheetExt_Private::initDialog(void)
// and 8 DLUs tall, plus 4 vertical DLUs for spacing. // and 8 DLUs tall, plus 4 vertical DLUs for spacing.
RECT tmpRect = {0, 0, 0, 8+4}; RECT tmpRect = {0, 0, 0, 8+4};
MapDialogRect(hDlgSheet, &tmpRect); MapDialogRect(hDlgSheet, &tmpRect);
SIZE descSize = {max_text_width, tmpRect.bottom}; const SIZE descSize = {max_text_width, tmpRect.bottom};
this->lblDescHeight = descSize.cy;
// Get the dialog margin. // Get the dialog margin.
// 7x7 DLU margin is recommended by the Windows UX guidelines. // 7x7 DLU margin is recommended by the Windows UX guidelines.
@ -2599,10 +2636,8 @@ void RP_ShellPropSheetExt_Private::initDialog(void)
// Adjust the rectangle for margins. // Adjust the rectangle for margins.
InflateRect(&dlgRect, -dlgMargin.left, -dlgMargin.top); InflateRect(&dlgRect, -dlgMargin.left, -dlgMargin.top);
// Calculate the size for convenience purposes. // Calculate the size for convenience purposes.
SIZE dlgSize = { dlgSize.cx = dlgRect.right - dlgRect.left;
dlgRect.right - dlgRect.left, dlgSize.cy = dlgRect.bottom - dlgRect.top;
dlgRect.bottom - dlgRect.top
};
// Current position. // Current position.
POINT headerPt = {dlgRect.left, dlgRect.top}; POINT headerPt = {dlgRect.left, dlgRect.top};
@ -2693,6 +2728,11 @@ void RP_ShellPropSheetExt_Private::initDialog(void)
// Hide subsequent tabs. // Hide subsequent tabs.
swpFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_HIDEWINDOW; swpFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_HIDEWINDOW;
// Store the D object pointer with this particular tab dialog.
SetProp(tab.hDlg, D_PTR_PROP, static_cast<HANDLE>(this));
// Store the D object pointer with this particular tab dialog.
SetProp(tab.hDlg, TAB_PTR_PROP, static_cast<HANDLE>(&tab));
// Current point should be equal to the margins. // Current point should be equal to the margins.
// FIXME: On both WinXP and Win7, ths ends up with an // FIXME: On both WinXP and Win7, ths ends up with an
// 8px left margin, and 6px top/right margins. // 8px left margin, and 6px top/right margins.
@ -2857,6 +2897,26 @@ void RP_ShellPropSheetExt_Private::initDialog(void)
} }
} }
// Update scrollbar settings.
// TODO: If a VScroll bar is added, adjust widths of RFT_LISTDATA.
// TODO: HScroll bar?
const auto tabs_cend = tabs.cend();
for (auto iter = tabs.cbegin(); iter != tabs_cend; ++iter) {
// Set scroll info.
// FIXME: Separate child dialog for no tabs.
const auto &tab = *iter;
// VScroll bar
SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = tab.curPt.y - 2; // max is exclusive
si.nPage = dlgSize.cy;
si.nPos = 0;
SetScrollInfo(tab.hDlg, SB_VERT, &si, TRUE);
}
// Initial update of RFT_MULTI_STRING fields. // Initial update of RFT_MULTI_STRING fields.
if (!vecStringMulti.empty()) { if (!vecStringMulti.empty()) {
def_lc = pFields->defaultLanguageCode(); def_lc = pFields->defaultLanguageCode();
@ -3203,7 +3263,7 @@ void RP_ShellPropSheetExt_Private::menuOptions_action_triggered(int menuId)
* @param uIdSubclass * @param uIdSubclass
* @param dWRefData RP_ShellPropSheetExt_Private * @param dWRefData RP_ShellPropSheetExt_Private
*/ */
LRESULT CALLBACK RP_ShellPropSheetExt_Private::DialogSubclassProc( LRESULT CALLBACK RP_ShellPropSheetExt_Private::MainDialogSubclassProc(
HWND hWnd, UINT uMsg, HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData) UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
@ -3212,13 +3272,13 @@ LRESULT CALLBACK RP_ShellPropSheetExt_Private::DialogSubclassProc(
case WM_NCDESTROY: case WM_NCDESTROY:
// Remove the window subclass. // Remove the window subclass.
// Reference: https://blogs.msdn.microsoft.com/oldnewthing/20031111-00/?p=41883 // Reference: https://blogs.msdn.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hWnd, DialogSubclassProc, uIdSubclass); RemoveWindowSubclass(hWnd, MainDialogSubclassProc, uIdSubclass);
break; break;
case WM_COMMAND: case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_RP_OPTIONS) { if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_RP_OPTIONS) {
// Pop up the menu. // Pop up the menu.
RP_ShellPropSheetExt_Private *const d = reinterpret_cast<RP_ShellPropSheetExt_Private*>(dwRefData); auto *const d = reinterpret_cast<RP_ShellPropSheetExt_Private*>(dwRefData);
assert(d->hBtnOptions != nullptr); assert(d->hBtnOptions != nullptr);
assert(d->hMenuOptions != nullptr); assert(d->hMenuOptions != nullptr);
if (!d->hBtnOptions || !d->hMenuOptions) if (!d->hBtnOptions || !d->hMenuOptions)
@ -3331,7 +3391,7 @@ void RP_ShellPropSheetExt_Private::createOptionsButton(void)
} }
// Subclass the parent dialog so we can intercept WM_COMMAND. // Subclass the parent dialog so we can intercept WM_COMMAND.
SetWindowSubclass(hWndParent, DialogSubclassProc, SetWindowSubclass(hWndParent, MainDialogSubclassProc,
static_cast<UINT_PTR>(IDC_RP_OPTIONS), static_cast<UINT_PTR>(IDC_RP_OPTIONS),
reinterpret_cast<DWORD_PTR>(this)); reinterpret_cast<DWORD_PTR>(this));
@ -3921,8 +3981,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
// FIXME: FBI's age rating is cut off on Windows // FIXME: FBI's age rating is cut off on Windows
// if we don't adjust for WM_SHOWWINDOW. // if we don't adjust for WM_SHOWWINDOW.
case WM_SHOWWINDOW: { case WM_SHOWWINDOW: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -3972,8 +4031,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_DESTROY: { case WM_DESTROY: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (d && d->lblIcon) { if (d && d->lblIcon) {
// Stop the animation timer. // Stop the animation timer.
d->lblIcon->stopAnimTimer(); d->lblIcon->stopAnimTimer();
@ -3991,8 +4049,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_NOTIFY: { case WM_NOTIFY: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4002,8 +4059,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_COMMAND: { case WM_COMMAND: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4013,8 +4069,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_PAINT: { case WM_PAINT: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4025,8 +4080,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
case WM_SYSCOLORCHANGE: case WM_SYSCOLORCHANGE:
case WM_THEMECHANGED: { case WM_THEMECHANGED: {
// Reload the images. // Reload the images.
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4068,8 +4122,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
// SPI_GETFONTSMOOTHING or SPI_GETFONTSMOOTHINGTYPE, // SPI_GETFONTSMOOTHING or SPI_GETFONTSMOOTHINGTYPE,
// but that message isn't sent when previewing changes // but that message isn't sent when previewing changes
// for ClearType. (It's sent when applying the changes.) // for ClearType. (It's sent when applying the changes.)
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (d) { if (d) {
// Update the fonts. // Update the fonts.
d->fontHandler.updateFonts(); d->fontHandler.updateFonts();
@ -4078,8 +4131,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_CTLCOLORSTATIC: { case WM_CTLCOLORSTATIC: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4095,8 +4147,7 @@ INT_PTR CALLBACK RP_ShellPropSheetExt_Private::DlgProc(HWND hDlg, UINT uMsg, WPA
} }
case WM_WTSSESSION_CHANGE: { case WM_WTSSESSION_CHANGE: {
RP_ShellPropSheetExt_Private *const d = static_cast<RP_ShellPropSheetExt_Private*>( auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
GetProp(hDlg, D_PTR_PROP));
if (!d) { if (!d) {
// No RP_ShellPropSheetExt_Private. Can't do anything... // No RP_ShellPropSheetExt_Private. Can't do anything...
return false; return false;
@ -4187,28 +4238,94 @@ UINT CALLBACK RP_ShellPropSheetExt_Private::CallbackProc(HWND hWnd, UINT uMsg, L
*/ */
INT_PTR CALLBACK RP_ShellPropSheetExt_Private::SubtabDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) INT_PTR CALLBACK RP_ShellPropSheetExt_Private::SubtabDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ {
// Propagate NM_CUSTOMDRAW to the parent dialog. switch (uMsg) {
if (uMsg == WM_NOTIFY) { case WM_DESTROY: {
const NMHDR *const pHdr = reinterpret_cast<const NMHDR*>(lParam); // Remove the D_PTR_PROP and TAB_PTR_PROP properties from the page.
switch (pHdr->code) { // The D_PTR_PROP property stored the pointer to the
case LVN_GETDISPINFO: // RP_ShellPropSheetExt_Private object.
case NM_CUSTOMDRAW: // The TAB_PTR_PROP property stored the pointer to the
case LVN_ITEMCHANGING: { // RP_ShellPropSheetExt_Private::tab object.
// NOTE: Since this is a DlgProc, we can't simply return RemoveProp(hDlg, RP_ShellPropSheetExtPrivate::D_PTR_PROP);
// the CDRF code. It has to be set as DWLP_MSGRESULT. RemoveProp(hDlg, RP_ShellPropSheetExtPrivate::TAB_PTR_PROP);
// References: return TRUE;
// - https://stackoverflow.com/questions/40549962/c-winapi-listview-nm-customdraw-not-getting-cdds-itemprepaint }
// - https://stackoverflow.com/a/40552426
INT_PTR result = SendMessage(GetParent(hDlg), uMsg, wParam, lParam); case WM_NOTIFY: {
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, result); // Propagate NM_CUSTOMDRAW to the parent dialog.
return true; const NMHDR *const pHdr = reinterpret_cast<const NMHDR*>(lParam);
switch (pHdr->code) {
case LVN_GETDISPINFO:
case NM_CUSTOMDRAW:
case LVN_ITEMCHANGING: {
// NOTE: Since this is a DlgProc, we can't simply return
// the CDRF code. It has to be set as DWLP_MSGRESULT.
// References:
// - https://stackoverflow.com/questions/40549962/c-winapi-listview-nm-customdraw-not-getting-cdds-itemprepaint
// - https://stackoverflow.com/a/40552426
INT_PTR result = SendMessage(GetParent(hDlg), uMsg, wParam, lParam);
SetWindowLongPtr(hDlg, DWLP_MSGRESULT, result);
return TRUE;
}
default:
break;
}
break;
}
case WM_VSCROLL: {
auto *const d = static_cast<RP_ShellPropSheetExt_Private*>(GetProp(hDlg, D_PTR_PROP));
auto *const tab = static_cast<RP_ShellPropSheetExt_Private::tab*>(GetProp(hDlg, TAB_PTR_PROP));
if (!d || !tab) {
// No RP_ShellPropSheetExt_Private or tab. Can't do anything...
break;
} }
default: // Check the operation and scroll there.
break; int deltaY = 0;
switch (LOWORD(wParam)) {
case SB_TOP:
deltaY = -tab->scrollPos;
break;
case SB_BOTTOM:
deltaY = tab->curPt.y - tab->scrollPos;
break;
case SB_LINEUP:
deltaY = -d->lblDescHeight;
break;
case SB_LINEDOWN:
deltaY = d->lblDescHeight;
break;
case SB_PAGEUP:
deltaY = -d->dlgSize.cy;
break;
case SB_PAGEDOWN:
deltaY = d->dlgSize.cy;
break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
deltaY = HIWORD(wParam) - tab->scrollPos;
break;
}
// Make sure this doesn't go out of range.
int scrollPos = tab->scrollPos + deltaY;
if (scrollPos < 0) {
deltaY -= scrollPos;
} else if (scrollPos + d->dlgSize.cy > tab->curPt.y) {
deltaY -= (scrollPos + 1) - (tab->curPt.y - d->dlgSize.cy);
}
tab->scrollPos += deltaY;
SetScrollPos(hDlg, SB_VERT, tab->scrollPos, TRUE);
ScrollWindow(hDlg, 0, -deltaY, nullptr, nullptr);
return TRUE;
} }
} }
// Dummy callback procedure that does nothing. // Dummy callback procedure that does nothing.
return false; // Let system deal with other messages return DefSubclassProc(hDlg, uMsg, wParam, lParam);
} }

View File

@ -91,7 +91,7 @@ END
*/ */
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_SUBTAB_CHILD_DIALOG DIALOGEX 0, 0, 16, 16 IDD_SUBTAB_CHILD_DIALOG DIALOGEX 0, 0, 16, 16
STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW | WS_TABSTOP STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW | WS_TABSTOP | WS_VSCROLL
EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT EXSTYLE WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT | WS_EX_CONTROLPARENT
FONT 8, "MS Shell Dlg", FW_NORMAL, 0, 0 FONT 8, "MS Shell Dlg", FW_NORMAL, 0, 0
BEGIN BEGIN