|
A simple, but very useful idea.
Just because the code works, it doesn't mean that it is good code.
|
|
|
|
|
|
IDS_NOITEM is undeclared identifier.i got error like this. Then i changed
CString sting;only
ans sting intialized with "there is no item to display"
But i got nothing in the listctrl...........
what can i do.............
|
|
|
|
|
This code has a few bugs:
1) When listview is in report mode, and resize the column header, the empty string is messed up.
2) When listview is displayed with a horizontal scroll bar, and moving this scroll bar will mess up the empty string.
Therefore, to correctly use the sample code, WM_HSCROLL, WM_VSCROLL and HDN_ITEMCHANGED message must also be processed.
|
|
|
|
|
Suggestion to draw the 'empty' message with default listview font and color:
Save the font and color in the prepaint stage of OnNMCustomdraw, and use those to draw the text in OnPaint.
It works, but I'm not 100% sure that the GDI objects saved during the prepaint stage will always be valid when used in OnPaint. Could someone tell me if I can safely assume they will?
// CMyListCtrl.h
class CMyListCtrl : public CListCtrl
{
public:
CMyListCtrl();
private:
HFONT m_hFont;
HBRUSH m_hBrush;
COLORREF m_crTextColor;
COLORREF m_crBkColor;
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnNMCustomdraw(
NMHDR *pNMHDR, LRESULT *pResult);
afx_msg void OnPaint();
};
// CMyListCtrl.cpp
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdraw)
ON_WM_PAINT()
END_MESSAGE_MAP()
CMyListCtrl::CMyListCtrl()
: m_hFont(NULL)
, m_hBrush(NULL)
, m_crTextColor(RGB(0,0,0))
, m_crBkColor(RGB(0,0,0))
{
}
void CMyListCtrl::OnNMCustomdraw(
NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMCUSTOMDRAW pNMCD = (LPNMCUSTOMDRAW)pNMHDR;
switch (pNMCD->dwDrawStage)
{
case CDDS_PREPAINT:
// Save font, brush, text and background color.
m_hFont = (HFONT)GetCurrentObject(
pNMCD->hdc, OBJ_FONT);
m_hBrush = (HBRUSH)GetCurrentObject(
pNMCD->hdc, OBJ_BRUSH);
m_crTextColor = GetTextColor(pNMCD->hdc);
m_crBkColor = GetBkColor(pNMCD->hdc);
break;
}
*pResult = CDRF_DODEFAULT;
}
void CMyListCtrl::OnPaint()
{
Default();
if (GetItemCount() <= 0)
{
// Compute the rectangle for the text
CRect rect; GetClientRect(rect);
CHeaderCtrl* pHeaderCtrl = GetHeaderCtrl();
if (pHeaderCtrl)
{
CRect rectHeader;
pHeaderCtrl->GetWindowRect(rectHeader);
rect.top += rectHeader.Height();
}
// Add spacing
DWORD dwItemSpacing =
ListView_GetItemSpacing(m_hWnd, TRUE);
rect.top += HIWORD(dwItemSpacing);
// Draw text, use font and brush saved during
// the PREPAINT custom draw stage, assuming
// they are still valid GDI objects.
ASSERT(m_hFont != NULL && m_hBrush != NULL);
HDC hdc = ::GetDC(m_hWnd);
int nSavedDC = ::SaveDC(hdc);
SetTextColor(hdc, m_crTextColor);
SetBkColor(hdc, m_crBkColor);
SelectObject(hdc, m_hFont);
FillRect(hdc, rect, m_hBrush);
DrawText(hdc,
_T("There are no items to show in this view."),
-1, rect,
DT_SINGLELINE|DT_CENTER|DT_TOP|DT_NOPREFIX);
RestoreDC(hdc, nSavedDC);
::ReleaseDC(m_hWnd, hdc);
}
}
|
|
|
|
|
If you include this code in a CListCtrl base class which you have in a DLL (my case!), trying to get the CHeaderCtrl object causes problems because of problems with permanent window maps (this is an awful mess in MFC).
So to get it to work when this code is not in the same DLL as the CListCtrl object, replace this:
CHeaderCtrl* pHC;<br />
pHC = GetHeaderCtrl();<br />
if (pHC != NULL)<br />
{<br />
CRect rcH;<br />
pHC->GetItemRect(0, &rcH);<br />
rc.top += rcH.bottom;<br />
}
with this:
HWND hWndHeader = ListView_GetHeader(m_hWnd);<br />
<br />
if (hWndHeader != NULL)<br />
{<br />
CRect rcH;<br />
pHC->GetItemRect( 0, &rcH );<br />
Header_GetItemRect(hWndHeader, 0, &rcH );<br />
rc.top += rcH.bottom;<br />
}
|
|
|
|
|
I want to be able to add row selection programmatically using different colors using vb 6 in a listview. I tried using the API SendMessage but got very strange results. Just say if I want to alternatley select rows using diffent colors.
|
|
|
|
|
After looking over the comments from others and giving it some thought (as well as some fiddling with my own code), I think I have found a better way.
I think it is better to perform this sort of thing in response to a WM_ERASEBKGND message (rather than WM_PAINT).
Also, if you are using Callbacks, you have to account for that as one of the messages mentions.
Considering those things, here's my take on it:
Add a function to handle the WM_ERASEBKGND message as follows:
BOOL CCallbackListCtrl::OnEraseBkgnd(CDC* pDC)
{
if(GetItemCount() <= 0)
{
int nSavedDC = pDC->SaveDC();
COLORREF clrText = ::GetSysColor(COLOR_WINDOWTEXT);
COLORREF clrBack = ::GetSysColor(COLOR_WINDOW);
CRect rc;
GetClientRect(&rc);
CHeaderCtrl* pHC = GetHeaderCtrl();
if(pHC != NULL)
{
CRect rcH;
pHC->GetItemRect(0, &rcH);
rc.top += rcH.bottom;
}
CString strText = "There are no items to display.";
pDC->SetTextColor(clrText);
pDC->SetBkColor(clrBack);
pDC->FillRect(rc, &CBrush(clrBack));
pDC->SelectStockObject(ANSI_VAR_FONT);
pDC->DrawText(strText, -1, rc,
DT_CENTER | DT_WORDBREAK | DT_NOPREFIX | DT_NOCLIP);
pDC->RestoreDC(nSavedDC);
ReleaseDC(pDC);
}
else
{
CListCtrl::OnEraseBkgnd(pDC);
}
return true;
}
That should do it. It works well for me (even when using Callbacks).
|
|
|
|
|
Beautiful!
Thanks!
|
|
|
|
|
But it doesn't work if list control has the LVS_EX_GRIDLINES extended style set, because the grid lines overlap the "no items" text.
|
|
|
|
|
Hmm. I never checked that.
Does it work OK the way the author does it, even with gridlines?
I generally don't use gridlines, so I never checked it (either the author's way or mine).
Thanks.
|
|
|
|
|
Yes, it works when the routine is written in the OnPaint function (after Default()). I guess the lines are drawn during this default msg processing call.
But as you wrote, and I agree with you, it is a better practice to use OnEraseBkGrnd. So I wonder how it would work if one removes temporarily the style-ex LVS_EX_GRIDLINES each time the control enters the 'empty' mode, then to reset it again as soon there is some new item inserted.
|
|
|
|
|
Hi-
I think I have found the problem regarding report mode; the problem is that your call to pHC->GetItemRect(0, &rcH) is incorrect, it should be pHC->GetClientRect(&rcH) instead. This populates the rect with the proper size of the header (rcH.bottom is 17 on my system). Now it works fine in report mode.
Thanks for the good code...it works great in my program.
|
|
|
|
|
Hmm. I'm a bit puzzled because:
1) I had not found any problems in Report Mode (which is the mode I most often use with a ListCtrl anyway), and
2) When I use pHC->GetItemRect(&rcH) (without the "0,"), it won't compile because the function requires that first parameter.
Perhaps I'm not understanding where you are suggesting the change.
|
|
|
|
|
I used GetClentRect(), which takes only a CRect. That worked for me whereas GetItemRect() did not.
Ron
|
|
|
|
|
it is bad coding habit to place this stuff into a WM_PAINT handler.
use WM_ERASEBKGND instead and you'll get a valid device context for free
|
|
|
|
|
When using it in OnEraseBkgnd(), the listview is not showing the text until a drag a selection rectangle over it. any suggestions?
|
|
|
|
|
I have a CFormView. I put 2 CtrlList's in the CFormView and made them both reports. I derive the class CEmptyCtrlList and I added the code and looked at articles in codeguru, but I just can't get this code to work. Is it me?
Do you have a demo project anywhere? My email address is brinasas@yahoo.com
Please, any response you can give me will be greatly appreciated.
Sincerely,
Danielle (an overworked graduate student)
|
|
|
|
|
Here is what I did to get this to work in a ClistView.
Changed
from: if (GetItemCount() <= 0)
to: if (GetListCtrl().GetItemCount() <= 0)
from: pHC = GetHeaderCtrl();
to: pHC = GetListCtrl().GetHeaderCtrl();
Added the following to my cpp implementation file
BEGIN_MESSAGE_MAP(CExCListCtrl, CListView)
//{{AFX_MSG_MAP(CExCListCtrl)
ON_WM_PAINT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
-
|
|
|
|
|
Thanks for submitting the above code but I'm having trouble figuring out how the control gets painted when the row count is greater than zero.
Your code works great when the control is empty but when I have rows in the control they are not visible.
It's probably just a simple explaination but I just don't get it. Thanks again for the pos
|
|
|
|
|
I found this article on Codeguru and Jeff Miller posted his modifications to the code. It seems I wasn't the only one with the problem.
http://codeguru.earthweb.com/mfc/comments/14265.shtml
|
|
|
|
|
There is a minor change required in order to use this mechanism in CALL BACK List control type.
In the OnPaint handler of the list control code change the code as follows...
void CListCtrlCon::OnPaint()
{
if (GetItemCount() <= 0)
{
CPaintDC dc(this); // Moved to inside the if loop
//Use the same code
....
}
else
CListCtrl::OnPaint(); //added to handle call back functions
}
|
|
|
|
|
This is nice but it looks slightly wrong when the list control is disabled since it still uses COLOR_WINDOW for the background.
(The colour is wrong but also there is a band of the correct disabled background colour between the list header adn the text.)
Not a big deal unless you disable your control, though!
|
|
|
|