CScrollbar SetScrollInfo has no effect - c++

I have similar problem to this one: How do you use MFC CScrollbar controls? but I figured out that my ON_WM_VSCROLL message is sending parameter nPos always equal 0. I thought that I should set the scrollbar with SetScrollInfo method or at least with SetScrollRange, and I try to do it in the PreCreateWindow() of a View class function (which is derived from CFormView).
Nevertheless the scrollbar doesn't seem to get data from the SCROLLINFO structure.
Here are my code samples:
BOOL CInterfaceView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
drawphoto=false; //other unrelated variables;
zoomfactor=1.0;
info1.cbSize=sizeof(SCROLLINFO); //SCROLLINFO global variable
info1.fMask=SIF_ALL;
info1.nMin=0;
info1.nMax=100;
info1.nPage=2;
info1.nPos=5;
info1.nTrackPos=2;
ScrollBar1.SetScrollInfo(&info1); //the vertical ScrollBar
// ScrollBar1.SetScrollRange(0,100); //this has no effect either
return CFormView::PreCreateWindow(cs);
}
VSCROLL message handler:
void CInterfaceView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
int CurPos = pScrollBar->GetScrollPos();
//debug code:
CString test;
int rn,rx;
pScrollBar->GetScrollRange(&rn,&rx);
test.Format(_T("%d %d %d\n"),nPos,CurPos,rx-rn);
if(pScrollBar!=NULL)
TRACE(test+_T(" dzialamy\n"));
//end debug code
//this part found on the Internet
// Determine the new position of scroll box.
switch (nSBCode)
{
case SB_LEFT: // Scroll to far left.
CurPos = 0;
break;
case SB_RIGHT: // Scroll to far right.
CurPos = 100;
break;
case SB_ENDSCROLL: // End scroll.
break;
case SB_LINELEFT: // Scroll left.
if (CurPos > 0)
CurPos--;
break;
case SB_LINERIGHT: // Scroll right.
if (CurPos < 100)
CurPos++;
break;
case SB_PAGELEFT: // Scroll one page left.
{
// Get the page size.
SCROLLINFO info;
ScrollBar1.GetScrollInfo(&info, SIF_ALL);
if (CurPos > 0)
CurPos = max(0, CurPos - (int) info.nPage);
}
break;
case SB_PAGERIGHT: // Scroll one page right
{
// Get the page size.
SCROLLINFO info;
ScrollBar1.GetScrollInfo(&info, SIF_ALL);
if (CurPos < 100)
CurPos = min(100, CurPos + (int) info.nPage);
}
break;
case SB_THUMBPOSITION: // Scroll to absolute position. nPos is the position
CurPos = nPos; // of the scroll box at the end of the drag operation.
break;
case SB_THUMBTRACK: // Drag scroll box to specified position. nPos is the
CurPos = nPos; // position that the scroll box has been dragged to.
break;
}
// Set the new position of the thumb (scroll box).
ScrollBar1.SetScrollPos(50); //orignally it was CurPos
CFormView::OnVScroll(nSBCode, 50, pScrollBar);
// ScrollBar1.SetScrollPos(nPos);
}
So I suspect, that I try to set the scrollbar either in wrong place, or do something wrong with it? I appreciate any help.

PreCreateWindow is called before the window (and its scroll bar) have been created. In a view class you should do the initialization in OnInitialUpdate. This is called after window creation but before the window becomes visible.

I think that PreCreateWindow() is too early to set up your scrollbar, the "proper" place would be when your CDocument-derived class has loaded/modified data which would affect the range of the scroll bar.

Related

CUSTOMDRAW in Win32 Header not appropriately drawing outside columns area

I am currently subclassing a ListView control to support custom color schemes - especially Dark Mode themes.
So first, I changed the ListView and associated Header with:
SetWindowTheme(hwndListView, isDarkModeEnabled() ? L"Explorer" : nullptr, nullptr);
SetWindowTheme(hwndListViewHeader, isDarkModeEnabled() ? L"ItemsView" : nullptr, nullptr);
I can set the ListView background color to any custom color I choose, while answering to the WM_THEMECHANGED message and doing a ListView_SetBkColor() macro.
Unfortunately, it seems I can't do it with the associated Header Control, I must resort to Custom Draw. So I did. I subclassed the ListView, because this is the window handle that will receive WM_NOTIFY messages from the Header control, and when responding to NM_CUSTOMDRAW message, my code looks like this:
case WM_NOTIFY:
{
LPNMHDR nmhdr = reinterpret_cast<LPNMHDR>(lParam);
if (nmhdr->code == NM_CUSTOMDRAW)
{
LPNMCUSTOMDRAW lpcd = reinterpret_cast<LPNMCUSTOMDRAW>(lParam);
switch (lpcd->dwDrawStage)
{
case CDDS_PREPAINT:
{
if (!PluginDarkMode::isEnabled())
return CDRF_DODEFAULT;
FillRect(lpcd->hdc, &lpcd->rc, getDarkerBackgroundBrush());
return CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYPOSTPAINT;
}
case CDDS_ITEMPREPAINT:
{
return DrawListViewHeaderItem(lParam); // This function draws the item entirely and then returns CDRF_SKIPDEFAULT.
}
case CDDS_POSTPAINT:
// Calculates the undrawn border outside columns
HWND hHeader = lpcd->hdr.hwndFrom;
int count = static_cast<int>(Header_GetItemCount(hHeader));
int colsWidth = 0;
RECT wRc = {};
for (int i = 0; i < count; i++)
{
Header_GetItemRect(hHeader, i, &wRc);
colsWidth += wRc.right - wRc.left;
}
RECT clientRect;
GetClientRect(hHeader, &clientRect);
if (clientRect.right > (colsWidth + 1))
{
clientRect.left = colsWidth + 1;
HDC hdc = GetDC(hHeader);
FillRect(hdc, &clientRect, getDarkerBackgroundBrush());
ReleaseDC(hHeader, hdc);
}
return CDRF_SKIPDEFAULT;
}
}
break;
}
Now, my problem is that, although this seems fine, my control will always end up with a black rectangle outside the column's area (the empty space where no column exists). And this effect persists up until I move the mouse outside the header control area.
For example:
And when the mouse leaves the area:
Any ideas of how to solve this?

Vertical scrollbar for a dialog in a CPropertySheet not working

I am a beginner in MFC. I have a dialog embedded in a property sheet.
Since the dialog is bigger than property sheet, some portion get cropped.
So I am planning to add a vertical scrollbar. I have tried two ways.
Added a scrollbar control from the toolbox in the dialog itself.
Created a control variable.
DDX_Control(pDX, IDC_SCROLLBAR, m_ctlScrollBar);
Added message map as below:
ON_WM_VSCROLL(IDC_SCROLLBAR,OnVScroll)
Added below code in OnInitDialog():
SCROLLINFO ScrollInfo;
ScrollInfo.cbSize = sizeof(ScrollInfo);
ScrollInfo.fMask = SIF_ALL;
ScrollInfo.nMin = 0;
ScrollInfo.nMax = 100;
ScrollInfo.nPage = 40;
ScrollInfo.nPos = 50;
ScrollInfo.nTrackPos = 0;
m_ctlScrollBar.SetScrollInfo(&ScrollInfo,TRUE);
OnVScroll() function overridden as below:
void CFeesPage::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
SCROLLINFO ScrollInfo;
m_ctlScrollBar.GetScrollInfo(&ScrollInfo);
switch (nSBCode)
{
case SB_BOTTOM: //Scrolls to the lower right.
break;
case SB_ENDSCROLL: //Ends scroll.
break;
case SB_LINEDOWN: //Scrolls one line down.
m_ctlScrollBar.SetScrollPos(m_ctlScrollBar.GetScrollPos() + 1,TRUE);
break;
case SB_LINEUP: //Scrolls one line up.
m_ctlScrollBar.SetScrollPos(m_ctlScrollBar.GetScrollPos() - 1,TRUE);
break;
case SB_PAGEDOWN: //Scrolls one page down.
m_ctlScrollBar.SetScrollPos(m_ctlScrollBar.GetScrollPos() + ScrollInfo.nPage, TRUE);
break;
case SB_PAGEUP: //Scrolls one page up.
m_ctlScrollBar.SetScrollPos(m_ctlScrollBar.GetScrollPos() - ScrollInfo.nPage, TRUE);
break;
case SB_THUMBPOSITION:
break;
case SB_THUMBTRACK:
m_ctlScrollBar.SetScrollPos(nPos, TRUE);
break;
case SB_TOP: //Scrolls to the upper left.
break;
}
}
In this case scrollbar moving, but child controls doesn't?
On another way, I have enabled scrollbar control for property sheet as below in OnInitDialog:
CScrollBar* test = this->GetScrollBarCtrl(SB_VERT);
Set SCROLLINFO as above.
The OnVScroll written as below:
void CSubTranSheet::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
SCROLLINFO ScrollInfo;
GetScrollInfo(SB_VERT, &ScrollInfo);
switch (nSBCode)
{
case SB_BOTTOM: //Scrolls to the lower right.
break;
case SB_ENDSCROLL: //Ends scroll.
break;
case SB_LINEDOWN: //Scrolls one line down.
SetScrollPos(SB_VERT, GetScrollPos(SB_VERT) + 1, TRUE);
break;
case SB_LINEUP: //Scrolls one line up.
SetScrollPos(SB_VERT, GetScrollPos(SB_VERT) - 1, TRUE);
break;
case SB_PAGEDOWN: //Scrolls one page down.
SetScrollPos(SB_VERT, GetScrollPos(SB_VERT) + ScrollInfo.nPage, TRUE);
break;
case SB_PAGEUP: //Scrolls one page up.
SetScrollPos(SB_VERT, GetScrollPos(SB_VERT) - ScrollInfo.nPage, TRUE);
break;
case SB_THUMBPOSITION:
break;
case SB_THUMBTRACK:
SetScrollPos(SB_VERT, nPos, TRUE);
break;
case SB_TOP: //Scrolls to the upper left.
break;
}
}
In this case also scrollbar moving but child dialog doesn't?
Please help me on this. I am not sure which method is right. Thanks in advance.
PropertySheet will pick the largest page dialog and it will resize itself so that all of the page dialogs are shown. Scrolling is not needed unless there is override for PropertySheet's size, or additional controls have been added in CMyPropertyPage::OnInitDialog
Moreover, the end-user's screen may have lower resolution, in which case parts of the propertysheet will be obscured. You just have to make smaller dialog pages, no taller than 1000 pixels, or about 300 dialog points.
The code shown in question is an attempt to update the scroller. In addition to updating the scroller, you have to scroll the dialog itself.
The link from #AndrewTruckle shows how to use ScrollWindow to achieve this.
Alternatively you can manually move all the child controls as shown below. This is somewhat easier because you can resize the dialog and adjust the scroller range without worrying about the child controls' alignment.
#include <map>
class CMyPropertyPage : public CPropertyPage
{
std::map<CWnd*, CRect> rc_children;
CRect rc_max;
void OnSize(UINT flags, int cx, int cy);
void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
DECLARE_MESSAGE_MAP()
};
void CMyPropertyPage::OnSize(UINT flags, int cx, int cy)
{
CPropertyPage::OnSize(flags, cx, cy);
CRect rc;
if(!rc_max.bottom)
{
//initialize once:
for(CWnd *p = GetWindow(GW_CHILD); p; p = p->GetWindow(GW_HWNDNEXT))
{
//save the rectangles for all child controls
p->GetWindowRect(&rc);
ScreenToClient(&rc);
rc_children[p] = rc;
//find the lowest point in dialog
if(rc.bottom > rc_max.bottom)
rc_max.bottom = rc.bottom;
}
}
GetClientRect(&rc);
SCROLLINFO info = { sizeof(info) };
info.fMask = SIF_ALL;
info.nMin = 0;
info.nMax = (rc_max.bottom + 100); //100 pixels below the lowest control
info.nPage = rc.bottom;
SetScrollInfo(SB_VERT, &info, TRUE);
}
void CMyPropertyPage::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CPropertyPage::OnVScroll(nSBCode, nPos, pScrollBar);
SCROLLINFO info = { sizeof(SCROLLINFO) };
GetScrollInfo(SB_VERT, &info, SIF_ALL);
//update scroller
switch(nSBCode)
{
case SB_LEFT: info.nPos = info.nMin; break;
case SB_RIGHT: info.nPos = info.nMax; break;
case SB_LINELEFT: info.nPos--; break;
case SB_LINERIGHT: info.nPos++; break;
case SB_PAGELEFT: info.nPos -= info.nPage; break;
case SB_PAGERIGHT: info.nPos += info.nPage; break;
case SB_THUMBPOSITION: info.nPos = info.nTrackPos; break;
case SB_THUMBTRACK: info.nPos = info.nTrackPos; break;
}
SetScrollInfo(SB_VERT, &info, TRUE);
if(info.nPos < 0 || info.nPos > rc_max.bottom)
return;
//find how many child controls we have
int count = 0;
for(CWnd *p = GetWindow(GW_CHILD); p; p = p->GetWindow(GW_HWNDNEXT))
count++;
//go through all child controls and move them:
HDWP hdwp = BeginDeferWindowPos(count);
for(CWnd *p = GetWindow(GW_CHILD); p; p = p->GetWindow(GW_HWNDNEXT))
{
CRect rc;
p->GetWindowRect(&rc);
ScreenToClient(&rc);
if(rc_children.find(p) != rc_children.end())
{
int y = info.nPos - rc_children[p].top;
DeferWindowPos(hdwp, p->m_hWnd, NULL, rc.left, -y, 0, 0,
SWP_NOSIZE | SWP_NOACTIVATE);
}
}
EndDeferWindowPos(hdwp);
}
BEGIN_MESSAGE_MAP(CMyPropertyPage, CPropertyPage)
ON_WM_VSCROLL()
ON_WM_SIZE()
END_MESSAGE_MAP()

How to block resizing after a double-click on a dialog window's top or bottom edges in Windows 10?

I'm coding a customized popup window with C++ using Win32. The condition for this popup window is that it can be only resized from the bottom down. The following is the implementation of such restriction:
RECT rcInitialWindowRectangle = {0};
//The dialog has WS_THICKFRAME style
LRESULT CALLBACK DlgWndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
case WM_INITDIALOG:
{
//Set minimum window size
::GetWindowRect(hDlg, &rcInitialWindowRectangle);
}
break;
case WM_SIZING:
{
//Restrict sizing on all sides but bottom
if(wParam != WMSZ_BOTTOM)
{
RECT* pRcWnd = (RECT*)lParam;
//Preserve all sides but bottom
int b = pRcWnd->bottom;
*pRcWnd = rcInitialWindowRectangle;
pRcWnd->bottom = b;
return TRUE;
}
}
break;
case WM_GETMINMAXINFO:
{
//The following is needed to restrict minimum window size
int w = rcInitialWindowRectangle.right - rcInitialWindowRectangle.left;
if(w != 0)
{
MINMAXINFO* pMMI = (MINMAXINFO*)lParam;
pMMI->ptMinTrackSize.x = w;
pMMI->ptMinTrackSize.y = rcInitialWindowRectangle.bottom - rcInitialWindowRectangle.top;
pMMI->ptMaxTrackSize.x = w;
}
}
break;
case WM_NCHITTEST:
{
//The following is needed to display correct cursor for resizing
POINT pnt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
RECT rcWnd;
::GetWindowRect(hDlg, &rcWnd);
//L, T, R, B
RECT rcBtm = {rcInitialWindowRectangle.left,
rcWnd.bottom - 16, //Some arbitrary border
rcInitialWindowRectangle.right,
rcWnd.bottom};
return ::PtInRect(&rcBtm, pnt) ? HTNOWHERE : HTBORDER;
}
break;
return 0;
}
So this works except one thing. On Windows 10, there's evidently a new feature -- when someone double-clicks on the bottom (or top) edge of a window -- here's an example with Notepad so that you can try:
that window is resized (stretched) to the top and bottom of the screen (akin to maximization, but only vertically.)
So my question is how do I block this double-click resizing? (In my case the top of the popup window should not move.)
PS. My first instinct was to block all double-clicks on the window's edge, but then I thought that maybe there's a less barbaric way to achieve this?
You are already handling WM_NCHITTEST. Handle WM_NCLBUTTONDBLCLK and don't forward to DefWindowProc unless the hit test (in wParam) indicates the lower frame.
here's the workaround that seems to work for me. It is not really about blocking the double-clicks. (Apart from subclassing the WndProc of a dialog box, which I haven't tried, I failed to block that double-click effect in DlgProc alone.) My workaround presented below is to achor the top of the popup window and let it drop down to the bottom of the screen, if the user who double-clicks the bottom border wants similar stuff to happen.
Since this does not answer my original question, I won't mark it as such.
//Add this case statement to my original code
case WM_WINDOWPOSCHANGING:
{
WINDOWPOS* pWP = (WINDOWPOS*)lParam;
if(!(pWP->flags & (SWP_NOMOVE | SWP_NOSIZE)))
{
int w = rcInitialWindowRectangle.right - rcInitialWindowRectangle.left;
if(w > 0)
{
//Anchor the top of the popup window
pWP->x = rcInitialWindowRectangle.left;
pWP->y = rcInitialWindowRectangle.top;
pWP->cx = w;
int h = pWP->cy;
//Make sure that the height fits the screen
POINT pnt = {pWP->x, pWP->y};
MONITORINFO mi = {0};
mi.cbSize = sizeof(mi);
if(::GetMonitorInfo(::MonitorFromPoint(pnt, MONITOR_DEFAULTTONEAREST), &mi))
{
if(pWP->y + h > mi.rcWork.bottom)
{
int nMinDefaultH = rcInitialWindowRectangle.bottom -
rcInitialWindowRectangle.top;
int nAh = mi.rcWork.bottom - y;
if(nAh >= nMinDefaultH)
h = nAh;
else
h = nMinDefaultH;
}
}
pWP->cy = h;
}
}
}
break;

CMFCListCtrl force selected item to have red color

I have my own CMFCListCtrl derived class where I implemented the
virtual COLORREF OnGetCellTextColor(int nRow, int nColum)
{
CMyClass* pMyClass = (CMyClass*)GetItemData(nRow);
if (pMyClass && pMyClass->m_bDeleted)
return RGB(255, 0, 0);
return __super::OnGetCellTextColor(nRow, nColum);
}
function for marking red the deleted entries. This works except when a item is selected.
I went to the void CMFCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) function and placed a breakpoint with the condition iRow==selected item on the line
lplvcd->clrText = OnGetCellTextColor(iRow, iColumn);
executed it, then I created a new Data Breakpoint for &lplvcd->clrText.
The Data Breakpoint got hit on function
comctl32.dll!SHThemeComputeTextColors()
, as the callstack image shows:
, which is clearly overriding the variable value.
As I search for SHThemeComputeTextColors on Internet and nothing appears, can somebody help me on forcing the selected items text to be red?
Changing the colors and fonts of highlighted items is trickier than it seems, because highlighting selected items is a system-wide issue. In fact, it was something I had delayed for a long time, and just today I've finally tackled...
There are at least two ways to change the looks of a list control: owner draw (you have to do all the drawing yourself) and custom draw (the system tells you when it is about to do some steps of the drawing and lets you change the color, the font, etc.). This answer is about custom draw. This article covers the basics of using Custom Draw with CListCtrl.
How to change the highlight color in a CListCtrl (not a CMFCListCtrl, we'll get to that soon) is explained in this other article. These are the steps you have to do:
Intercept the listview draw routine just before it is about to draw a highlighted row (item).
Turn off the row highlight.
Set the row colors to whatever you want.
Let the listview draw the row.
Intercept the listview draw routine after it has drawn the row (post-draw item).
Turn this row's highlighting back on.
So, when the listview is about to draw a highlighted row, you have to telll the system the row is not highlighted so it doesn't use the system colors to paint it, tell what color to use, and set the item as highlighted again, so the selection works as usual.
Here is the code from that article:
COLORREF g_MyClrFgHi; // My foreground hilite color
COLORREF g_MyClrBgHi; // My background hilite color
HWND g_hListView; // Window handle of listview control
void EnableHighlighting(HWND hWnd, int row, bool bHighlight)
{
ListView_SetItemState(hWnd, row, bHighlight? 0xff: 0, LVIS_SELECTED);
}
bool IsRowSelected(HWND hWnd, int row)
{
return ListView_GetItemState(hWnd, row, LVIS_SELECTED) != 0;
}
bool IsRowHighlighted(HWND hWnd, int row)
{
// We check if row is selected.
// We also check if window has focus. This was because the original listview
// control I created did not have style LVS_SHOWSELALWAYS. So if the listview
// does not have focus, then there is no highlighting.
return IsRowSelected(hWnd, row) && (::GetFocus(hWnd) == hWnd);
}
BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
static bool bIsHighlighted = false;
*pResult = 0;
NMHDR *p = (NMHDR *)lParam;
switch (p->code)
{
...
case NM_CUSTOMDRAW:
NMLVCUSTOMDRAW *lvcd = (NMLVCUSTOMDRAW *)p;
NMCUSTOMDRAW &nmcd = lvcd->nmcd;
switch (nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
// We want item prepaint notifications, so...
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int iRow = (int)nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(g_hListView, iRow);
if (bHighlighted)
{
lvcd->clrText = g_MyClrFgHi; // Use my foreground hilite color
lvcd->clrTextBk = g_MyClrBgHi; // Use my background hilite color
// Turn off listview highlight otherwise it uses the system colors!
EnableHighlighting(g_hListView, iRow, false);
}
// We want item post-paint notifications, so...
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
break;
}
case CDDS_ITEMPOSTPAINT:
{
if (bHighlighted)
{
int iRow = (int)nmcd.dwItemSpec;
// Turn listview control's highlighting back on now that we have
// drawn the row in the colors we want.
EnableHighlighting(g_hListView, iRow, true);
}
*pResult = CDRF_DODEFAULT;
break;
}
default:
*pResult = CDRF_DODEFAULT;
break;
}
break;
...
}
}
This works fine with a CListCtrl, but you are asking about a CMFCListCtrl. The problem is that CMFCListCtrl is already asking to be notified about NM_CUSTOMDRAW notifications (that's when it calls the OnGetCellTextColor function, as you've seen). If you create a handler for that in your own CMFCListCtrl-derived class, those notifications won't get to CMFCListCtrl and you lose that functionality (it may fine, depending on your needs).
So here is what I've done: I've created a NM_CUSTOMDRAW handler in my list control and, if I'm dealing with a highlighted row, I change the colors, otherwise, I call CMFCListCtrl::OnNMCustomDraw(). As you'll see, I just use the normal highlight colors; that's because I just wanted to see the selected items even when the control doesn't have the focus:
void CMyListCtrl::OnNMCustomdraw(NMHDR* pNMHDR, LRESULT* pResult)
{
bool callParent = true;
static bool bHighlighted = false;
LPNMLVCUSTOMDRAW lpLVCustomDraw = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
NMCUSTOMDRAW nmcd = lpLVCustomDraw->nmcd;
*pResult = CDRF_DODEFAULT;
switch (lpLVCustomDraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int row = nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(row);
if (bHighlighted)
{
lpLVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
lpLVCustomDraw->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
EnableHighlighting(row, false);
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
callParent = false;
}
}
break;
case CDDS_ITEMPOSTPAINT:
if (bHighlighted)
{
int row = nmcd.dwItemSpec;
EnableHighlighting(row, true);
callParent = false;
}
*pResult = CDRF_DODEFAULT;
break;
default:
break;
}
if (callParent)
{
__super ::OnCustomDraw(pNMHDR, pResult);
}
}
bool CMyListCtrl::IsRowHighlighted(int row)
{
bool selected = GetItemState(row, LVIS_SELECTED) != 0;
return selected;
}
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
}
I haven't tested it thoroughly, but it seems to work.
UPDATE:
There is a little problem. When you call EnableHigilighting(), the dialog will get LVN_ITEMCHANGED notifications, which can trigger a redraw, which will trigger a LVN_ITEMCHANGED notification... So if you are listening to LVN_ITEMCHANGED notifications, you might need to do something like this:
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
m_internalStateChange = true;
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
m_internalStateChange = false;
}
void CWhateverDialog::OnLvnItemchangedListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if (!c_List.InternalStateChange() && /* other conditions */)
{
// Respond to state changes
}
}

The entire scrollbar moves in a CWnd when calling ScrollWindowEx with SW_SCROLLCHILDREN

I've added a CScrollBar to my Cwnd named CPanel. But when I scroll the page the whole scrollbar moves. Any idea how I can solve this? Changing CPanel to a CScrollView or CFormView is sadly not an option.
CPanel::CPanel()
{
CreateEx(WS_EX_CONTROLPARENT, _T("Static"), NULL, WS_CHILD | WS_TABSTOP | WS_BORDER, m_clRect, pwndParent, IDC_PANEL_FORM);
ScrollBarInit();
}
void CPanel::ScrollBarInit()
{
//Before this i calculate size of scrollbar and size of scrollarea
m_pclScrollBar = new CScrollBar();
m_pclScrollBar->Create(WS_CHILD | WS_VISIBLE | SBS_VERT | SBS_RIGHTALIGN, clRectScrollbar, this, IDC_SCROLLBAR_FORM);
m_pclScrollBar->SetScrollRange(VSCROLL_RANGE_MIN, VSCROLL_RANGE_MAX);
//After this I add scrollbar info
}
void CPanel::OnVScroll(UINT iSBCode, UINT iPos, CScrollBar* pclScrollBar)
{
switch(pclScrollBar->GetDlgCtrlID())
{
case IDC_SCROLLBAR_FORM:
ScrollBarScroll(iSBCode, iPos, pclScrollBar);
break;
}
}
void CPanel::ScrollBarScroll(UINT iSBCode, UINT iPos, CScrollBar *pclScrollBar)
{
int iScrollPositionPrevious;
int iScrollPosition;
int iScrollPositionOriginal;
iScrollPositionOriginal = m_pclScrollBar->GetScrollPos();
iScrollPosition = iScrollPositionOriginal;
if(m_pclScrollBar != NULL)
{
SCROLLINFO info = {sizeof( SCROLLINFO ), SIF_ALL};
pclScrollBar->GetScrollInfo(&info, SB_CTL);
pclScrollBar->GetScrollRange(&info.nMin, &info.nMax);
info.nPos = pclScrollBar->GetScrollPos();
iScrollPositionPrevious = info.nPos;
switch(iSBCode)
{
case SB_TOP: // Scroll to top
iScrollPosition = VSCROLL_RANGE_MIN;
break;
case SB_BOTTOM: // Scroll to bottom
iScrollPosition = VSCROLL_RANGE_MAX;
break;
case SB_ENDSCROLL: // End scroll
break;
case SB_LINEUP: // Scroll one line up
if(iScrollPosition - VSCROLL_LINE >= VSCROLL_RANGE_MIN)
iScrollPosition -= VSCROLL_LINE;
else
iScrollPosition = VSCROLL_RANGE_MIN;
break;
case SB_LINEDOWN: // Scroll one line down
if(iScrollPosition + VSCROLL_LINE <= VSCROLL_RANGE_MAX)
iScrollPosition += VSCROLL_LINE;
else
iScrollPosition = VSCROLL_RANGE_MAX;
break;
case SB_PAGEUP: // Scroll one page up
{
// Get the page size
SCROLLINFO scrollInfo;
m_pclScrollBar->GetScrollInfo(&scrollInfo, SIF_ALL);
if(iScrollPosition > VSCROLL_RANGE_MIN)
iScrollPosition = max(VSCROLL_RANGE_MIN, iScrollPosition - VSCROLL_PAGE);
break;
}
case SB_PAGEDOWN: // Scroll one page down
{
// Get the page size
SCROLLINFO scrollInfo;
m_pclScrollBar->GetScrollInfo(&scrollInfo, SIF_ALL);
if(iScrollPosition < VSCROLL_RANGE_MAX)
iScrollPosition = min(VSCROLL_RANGE_MAX, iScrollPosition + VSCROLL_PAGE);
break;
}
case SB_THUMBPOSITION: // Scroll to the absolute position. The current position is provided in nPos
case SB_THUMBTRACK: // Drag scroll box to specified position. The current position is provided in nPos
iScrollPosition = iPos;
break;
default:
break;
}
if(iScrollPositionOriginal != iScrollPosition)
{
m_pclScrollBar->SetScrollPos(iScrollPosition);
ScrollWindowEx(0, iScrollPositionOriginal - iScrollPosition, NULL, NULL, NULL, NULL, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
}
}
}
Since you specified SW_SCROLLCHILDREN in your call to ScrollWindowEx (see also the Windows API documentation for ScrollWindowEx; it is usually better than MFC's), and requested the entire client area to be scrolled by passing NULL for the lpRectScroll parameter, the system does just that. The scrollbar is a child window as well, so it gets moved like all other child controls.
The solution is hinted to in the documentation for SW_SCROLLCHILDREN:
Scrolls all child windows that intersect the rectangle pointed to by lpRectScroll by the number of pixels specified in dx and dy.
To prevent the scrollbar from moving alongside the other child windows, it must be excluded from the rectangle passed as the lpRectScroll parameter. To do so, query the client area, and subtract the area occupied by the scrollbar. Assuming that the scrollbar is at the right and covers the entire height, the following code will solve your issue:
if(iScrollPositionOriginal != iScrollPosition) {
m_pclScrollBar->SetScrollPos(iScrollPosition);
// Query the window's client area
CRect clientArea;
GetClientRect(clientArea);
// Find the area occupied by the scrollbar
CRect scrollbarArea;
m_pclScrollBar->GetWindowRect(scrollbarArea);
// Adjust the client area to exclude the scrollbar area
CRect scrollArea(clientArea);
scrollArea.DeflateRect(0, 0, scrollbarArea.Width(), 0);
ScrollWindowEx(0, iScrollPositionOriginal - iScrollPosition, scrollArea, NULL,
NULL, NULL, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
}
Also keep in mind the remark on using the SW_SCROLLCHILDREN flag:
If the SW_SCROLLCHILDREN flag is specified, Windows will not properly update the screen if part of a child window is scrolled. The part of the scrolled child window that lies outside the source rectangle will not be erased and will not be redrawn properly in its new destination. Use the DeferWindowPos Windows function to move child windows that do not lie completely within the lpRectScroll rectangle.
Since you don't have control over the amount of pixels the user will scroll, there will be situations where the screen will not properly update. To work around this, implement a solution following the procedure outlined in the quote above: Replace the call to ScrollWindowEx with a series of calls to DeferWindowPos, repositioning all child windows manually.