Get row height of a CListCtrl in MFC - c++

I want to resize my CListCtrl as Report for displaying only n rows with text or empty. And I need the height of a row (they all are of the same size and constant).
CRect rect;
myList.GetWindowRect(&rect);
ScreenToClient(&rect);
myList.MoveWindow(rect.left, rect.top, rect.Width(), 16*8-5 /* TODO */);

CListCtrl with Variable Row Height
/*
1a. Setup a typical CListCtrl with owner draw
1b. Fill the CListCtrl with the text you want, as you would normally
2. Setup a CListBox with OwnerDrawVariable and NO border
3. Make the ListBox a child of the ListCtrl
4. Use OnSize to position and OnDrawItem to display the text
Note the OnDrawItem is using the text and some parameters from the CListCtrl
so be mindful of m_lbTest vs. m_lcTest.
*/
void CTestDlg::FillListCtrl()
{
// fill CListCtrl with some text
int x,y;
int ColCount;
CString csStr;
m_lbTest.SetParent(&m_lcTest);
ColCount=m_lcTest.GetHeaderCtrl()->GetItemCount();
for (y=0; y<10; y++) {
m_lcTest.InsertItem(y,"");
for (x=0; x<ColCount; x++) {
csStr.Format("y=%d x=%d",y,x);
m_lcTest.SetItemText(y,x,csStr);
}
m_lbTest.AddString("");
m_lbTest.SetItemHeight(y,20 + y*10);
}
}
void CTestDlg::OnSize(UINT nType, int cx, int cy)
{
int x,y;
CRect rb;
if (m_lcTest.m_hWnd != NULL) {
y=0;
x=0;
m_lcTest.SetWindowPos(&wndTop,x,y,cx,cy-y,SWP_NOZORDER);
m_lcTest.GetHeaderCtrl()->GetClientRect(&rb);
x=0;
y+=rb.Height();
m_lbTest.SetWindowPos(&wndTop,x,rb.Height(),cx-x-4,cy-y-4,SWP_NOZORDER);
}
}
void CTestDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpD)
{
CString csStr;
CRect rc;
CRect rb;
UINT fmt;
LVCOLUMN lvc;
int x,y;
CDC dc;
int ColCount;
CPen cPen;
CopyRect(&rc,&lpD->rcItem);
dc.Attach(lpD->hDC);
dc.SetBkMode(TRANSPARENT);
dc.SelectObject(GetStockObject(DEFAULT_GUI_FONT));
cPen.CreatecPen.CreatePen(PS_SOLID,1,RGB(192,192,192));
dc.SelectObject(cPen);
dc.SetTextColor(RGB(0,0,0));
if (m_lbTest.m_hWnd!=NULL && nIDCtl==IDC_LB_TEST) {
y=lpD->itemID;
if (y >= 0) {
rc.left+=5;
ColCount=m_lcTest.GetHeaderCtrl()->GetItemCount();
for (x=0; x<ColCount; x++){
rc.right=rc.left + m_lcTest.GetColumnWidth(x) - 10;
rb.top =rc.top+1;
rb.bottom=rc.bottom;
rb.left =rc.left - 5;
rb.right =rc.right + 4;
if ((lpD->itemState&ODS_SELECTED) != 0) dc.FillSolidRect(&rb,RGB(255,255,192)); // light yellow
else dc.FillSolidRect(&rb,RGB(255,255,255));
rb.top--;
dc.MoveTo(rb.left-1,rb.bottom);
dc.LineTo(rb.right, rb.bottom);
dc.LineTo(rb.right, rb.top);
dc.LineTo(rb.left-1,rb.top);
lvc.mask=LVCF_FMT;
m_lcTest.GetColumn(x,&lvc);
if ((lvc.fmt&LVCFMT_RIGHT) != 0) fmt=DT_RIGHT;
else if ((lvc.fmt&LVCFMT_CENTER) != 0) fmt=DT_CENTER;
else fmt=DT_LEFT;
fmt|=DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX;
csStr=m_lcTest.GetItemText(y,x);
dc.DrawText(csStr,&rc,fmt);
rc.left=rc.right + 10;
}
}
}
cPen.DeleteObject();
dc.Detach();
}

Related

Getting an error "This was 0x35" in C++ using SDL2

I'm trying to make a simple game engine in C++ and I keep getting a weird error that says "This was 0x35":
I'm using visual studio and SDL2 (although I use it more like SDL). When the error occurs, the program has started to execute and is "Still running" meaning I have to stop it manually. The exception shows up in this function:
void Tab::Draw(int x, int y, SDL_Surface* display, bool setpos = true)
{
if (setpos) {
this->rect->x = x; //This is the line that the exception points to.
this->rect->y = y;
}
SDL_BlitScaled(surf, NULL, display, rect);
}
The variable 'rect' is an SDL_Rect
The variable 'surf' is an SDL_Surface
both variables get initialized here:
Tab::Tab(SDL_Surface* Image, SDL_Rect* Rect) {
surf = Image;
if (Rect != NULL) {
rect = Rect;
}
}
and this is the only code that calls the function Tab::Draw:
void Bar::Draw(SDL_Surface* display)
{
SDL_BlitScaled(image, NULL, display, &rect);
for (int i = 0; i < 20; i++) {
tabs[i]->Draw(0, 0, display, true);
}
}
The bar class is meant to be a line with a bunch of tabs(which I should and will rename to Icon at some point)
Where Bar::Draw gets called:
#include "AppClass.h"
void CApp::OnRender() {
//Draw stuff
SDL_FillRect(window, NULL, SDL_MapRGB(window->format, r, g, b));
main.Draw(window);
ToBlit->Rect.x = mouseX - 32;
ToBlit->Rect.y = mouseY - 32;
ToBlit->Rect.w = 64;
ToBlit->Rect.h = 64;
if (SDL_GetMouseFocus() == display) {
SDL_BlitScaled(ToBlit->Surface, NULL, window, &ToBlit->Rect);
}
//Update
SDL_UpdateWindowSurface(display);
}

MFC CScrollView does not clear background

My English is not perfect. I am using Visual C++ 2019 and MFC. Example program: an SDI program, the base of the view is CScrollView, draws 128*128 0s in a matrix. But MFC does not clear the background at scrolling with the scrollbar. Have you an idea? Thank you.
In settings of Windows, I am using 96 dpi * 3 = 288 dpi.
I tried: 96 dpi mode is affected so.
How can I upload the example program to this?
void CsdView::OnDraw(CDC *pDC) {
CsdDoc *pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CRect rect;
GetClientRect(&rect);
pDC->FillSolidRect(rect, 0xFFFFFF);
CPoint pos = GetDeviceScrollPosition();
TRACE(L"OnDraw: %4u.%4u - %4u.%4u, %4u.%4u\n", rect.right, rect.bottom, pos.x, pos.y, rect.right + pos.x, rect.bottom+pos.y);
for (int i = 0; i < 128; ++ i)
for (int j = 0; j < 128; ++ j)
pDC->TextOutW(j*20 - pos.x, i*54 - pos.y, L"0", 1);
}
void CsdView::OnInitialUpdate() {
CScrollView::OnInitialUpdate();
CSize sizeTotal;
sizeTotal.cx = 20*128;
sizeTotal.cy = 54*128;
SetScrollSizes(MM_TEXT, sizeTotal);
}
BOOL CsdView::OnEraseBkgnd(CDC *pDC) {
CBrush brush(0xFFFFFF);
FillOutsideRect(pDC, &brush);
return TRUE;
// return CScrollView::OnEraseBkgnd(pDC);
}
I can not upload picture and code as a comment, so I must edit the original question.
A little bug is remained. The orginal code (MDI MFC):
void CIDEView::OnDraw(CDC *pDC) {
CIDEDoc *const d = GetDocument();
ASSERT_VALID(d);
if (! d)
return;
CPoint const pos = GetDeviceScrollPosition();
CRect rect;
GetClientRect(&rect);
OffsetRect(&rect, pos.x, pos.y);
pDC->FillSolidRect(rect, bkcolor);
auto oldfont = pDC->SelectObject(&font);
pDC->SetBkColor(bkcolor);
pDC->SetTextColor(textcolor);
pDC->SetBkMode(TRANSPARENT);
const int cxs = pos.x / mincw, cys = pos.y / lineheight;
const int cxe = (rect.right + mincw-1) / mincw,
cye = (rect.bottom + 41) / lineheight;
for (int i = cys; i <= cye; ++ i)
for (int j = cxs; j <= cxe; ++ j)
pDC->TextOutW(textmargin+j*mincw, i*lineheight, L"0", 1);
pDC->SelectObject(oldfont);
}
void CIDEView::OnInitialUpdate() {
CScrollView::OnInitialUpdate();
SetScrollSizes(MM_TEXT, {linewidth, 128*lineheight});
}
BOOL CIDEView::OnEraseBkgnd(CDC *pDC) {
return TRUE;
}
The CScrollView class is a view with scrolling capabilities. You use it almost like a CView (ie drawing in the OnDraw() member), only you have to take into account the possible scrolling.
The GetClientRect() function returns the visible client area, and the coordinates returned are not relative to the view origin, but to the window origin, ie the left and top members are always 0. The CDC parameter in the OnDraw() function though are relative to the logical view origin, so an adjustment is needed.
As for your code, you don't need to use the OnEraseBkgnd() function, because you do so in OnDraw(). You fill only the visible part of the window, but that's very much OK. So it would best to remove it. Also, the coordinates passed to the TextOutW() function must be relative to the view origin, so the -pos.x and -pos.y adjustments are wrong. Instead, it's the rectanlge passed to the FillSolidRect() function that needs to be adjusted. So, your code would become:
void CsdView::OnDraw(CDC *pDC)
{
CsdDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CPoint pos = GetScrollPosition();
CRect rect;
GetClientRect(&rect);
// Adjust client rect to device coordinates
OffsetRect(&rect, pos.x, pos.y);
pDC->FillSolidRect(rect, 0xFFFFFF);
for (int i = 0; i < 128; ++i)
for (int j = 0; j < 128; ++j)
pDC->TextOutW(j * 20, i * 54, L"0", 1);
}
However this code is "wasteful", as it paints the whole view. It can be optimized to paint only the visible part. It will draw only the 0s for which even one pixel lies in the visible part (didn't #define anything, just used your hard-coded 20 and 54 values). Also changed the color to yellow, so you can test it better (white is the default background color).
void CsdView::OnDraw(CDC *pDC)
{
CsdDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CPoint pos = GetScrollPosition();
CRect rect;
GetClientRect(&rect);
// Adjust client rect to device coordinates
OffsetRect(&rect, pos.x, pos.y);
pDC->FillSolidRect(rect, 0x00FFFF);
// Paint only the items in the visible part
int xL = rect.left / 20,
xR = (rect.right + 19) / 20,
yT = rect.top / 54,
yB = (rect.bottom + 53) / 54;
for (int i = yT; i < yB; ++i)
for (int j = xL; j < xR; ++j)
pDC->TextOutW(j * 20, i * 54, L"0", 1);
}
EDIT:
In the revised code, why are the doc, pos, cxs etc variables const? There are also quite a few bugs in your logic:
You set your view size in OnInitialUpdate(), rather assuming that linewidth equals to textmargin + 128*mincw. If not, revise your code again.
The rect is already adjusted (using OffsetRect()), so it is wrong to add pos.x again.
Since you have the cell sizes in variables, don't use hard-coded numbers. For example the code for cxe should become cxe = (rect.right + mincw - 1) / mincw; Update the cye code similarly.
Also you paint at an offset of textmargin. The code should then become cxe = (rect.right - textmargin + mincw - 1) / mincw;
The code I posted works OK with the < condition in the loops, you don't need <=. Do the math and you will find that this is the correct one.

SDL2 double buffer not working, still tearing

I need a double buffer because i'm starting to notice tearing when I want to move my texture made tile map around the screen via mouse click.
I'm using SDL2 and this is a SDL2 specific question, check out my code that produces tearing, whats wrong?
//set up buffer and display
bufferTexture = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_STREAMING,800,600);
displayTexture = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,800,600);
while(running)
{
handleEvents();
SDL_SetRenderTarget(renderer,bufferTexture); // everything goes into this buffer
SDL_RenderClear(renderer); // clear buffer before draw
gTileMovement.updateMapCoordinates();
for(int i = 0; i < MAP_ROWS; i++)//rows
{
for(int j = 0; j < MAP_COLUMNS; j++)//columns
{
x = (j * 100) - (i * 100);
y = ((i * 100) + (j * 100)) / 2;
drawTiles(i,j,x,y);
}
}
//move from buffer to display texture
memcpy(&displayTexture,&bufferTexture,sizeof((&bufferTexture)+1));
//change render target back to display texture
SDL_SetRenderTarget(renderer,displayTexture);
//show it all on screen
SDL_RenderPresent(renderer);
}
for all it matters here is my drawTiles function too, is this not conventional? :
void drawTiles(int i,int j,int x,int y)
{
//updates based on a mouse clicks xy coords
gTileMovement.updateMapCoordinates();
if(tileMap[i][j] == 1) // grass?
{
gSpriteSheetTexture.render(x+gTileMovement.getUpdatedX(),y+gTileMovement.getUpdatedY(),&gSpriteClips[1]);
}
if(tileMap[i][j] == 0) // wall?
{
gSpriteSheetTexture.render(x+gTileMovement.getUpdatedX(),y+gTileMovement.getUpdatedY(),&gSpriteClips[0]);
}
if(tileMap[i][j] == 2) // tree?
{
gSpriteSheetTexture.render(x+gTileMovement.getUpdatedX(),y+gTileMovement.getUpdatedY(),&gSpriteClips[2]);
}
}
Which followes into how I SDL_RenderCopy the tiles through a class. This copies the textures onto the current targeted renderer does it not? Which is the buffer texture if i'm not mistaken.
void LTexture::render(int x, int y, SDL_Rect * clip)
{
SDL_Rect renderQuad = {x, y, mWidth, mHeight};
if(clip != NULL)
{
renderQuad.w = clip->w;
renderQuad.h = clip->h;
}
SDL_RenderCopy(renderer, mTexture, clip, &renderQuad);
}

MFC Custom tick marks are erased on slider move

I am drawing the Custom slider control. But my tic marks are getting erased on moving the thumb. I draw the ticks in ITEMPREPAINT as below:
if( lpcd->dwDrawStage == CDDS_ITEMPREPAINT )
{
if(lpcd->dwItemSpec == TBCD_TICS)
{
CDC *pDC = CDC::FromHandle(lpcd->hdc);
INT nMin=0,nMax=0,range;
GetRange(nMin,nMax);
range = nMax - nMin;
INT num_tics = 4;
INT frequency = range/num_tics;
CRect channelRect, thumbRect;
GetChannelRect(&channelRect);
GetThumbRect(&thumbRect);
INT width,pos=channelRect.left;
/*pDC->MoveTo(pos,channelRect.bottom);
pDC->LineTo(pos,channelRect.bottom+10);*/
for (INT tic=0; tic<num_tics; tic++)
{
width = channelRect.right - channelRect.left;
pos += width/num_tics;
pDC->MoveTo(pos,channelRect.bottom);
pDC->LineTo(pos,channelRect.bottom+10);
}
*pResult = CDRF_SKIPDEFAULT;
return;
}
}

How do I prevent flickering on CListCtrl?

I'm using a CListCtrl/CListView report view (LVS_REPORT) in virtual mode (LVS_OWNERDATA) with LVS_EX_DOUBLEBUFFER enabled and I encounter ugly flickering. Double buffer have a real effect but it doesn't stop all flickering (without it very slow).
I'm not looking for switching to other controls that would require a high amount of rework (like ObjectListView)
How does the flickering behaves:
on column resize - the background is first clean using lightgray and after this is displayed the text (background is white)
on mouse scroll (animated) - for a very short time there is lightgray-bar displayed in the area where new lines are to be displayed.
It looks like it does clean the background using the default window background color (lightgray) for the area where it has to redraw.
How do I solve the flickering problem?
Try to do the following:
- Set Clip Children and Clip Sibling for paremt dialog of List Control.
- Make dirived from CListCtrl class. In this class overwrite OnEraseBkgnd. In the OnEraseBkgnd fill with background color area around of visible items of the list.
The OnEraseBkgnd can look like:
BOOL CListCtrlEx::OnEraseBkgnd(CDC* pDC)
{
CBrush br;
CRect rcCli;
CRect rcItemsRect(0, 0, 0, 0);
int nHeadHeight = 0;
int nItems = GetItemCount();
GetClientRect(&rcCli);
CHeaderCtrl* pHeadCtrl = GetHeaderCtrl();
if (pHeadCtrl)
{
CRect rcHead;
pHeadCtrl->GetWindowRect(&rcHead);
nHeadHeight = rcHead.Height();
}
rcCli.top += nHeadHeight;
if (nItems > 0)
{
CPoint ptItem;
CRect rcItem;
GetItemRect(nItems - 1, &rcItem, LVIR_BOUNDS);
GetItemPosition(nItems - 1, &ptItem);
rcItemsRect.top = rcCli.top;
rcItemsRect.left = ptItem.x;
rcItemsRect.right = rcItem.right;
rcItemsRect.bottom = rcItem.bottom;
if (GetExtendedStyle() & LVS_EX_CHECKBOXES)
rcItemsRect.left -= GetSystemMetrics(SM_CXEDGE) + 16;
}
br.CreateSolidBrush(GetBkColor());
if (rcItemsRect.IsRectEmpty())
pDC->FillRect(rcCli, &br);
else
{
if (rcItemsRect.left > rcCli.left) // fill left rectangle
pDC->FillRect(
CRect(0, rcCli.top, rcItemsRect.left, rcCli.bottom), &br);
if (rcItemsRect.bottom < rcCli.bottom) // fill bottom rectangle
pDC->FillRect(
CRect(0, rcItemsRect.bottom, rcCli.right, rcCli.bottom), &br);
if (rcItemsRect.right < rcCli.right) // fill right rectangle
pDC->FillRect(
CRect(rcItemsRect.right, rcCli.top, rcCli.right, rcCli.bottom), &br);
}
return TRUE;
}
I know only way to have flicker free is using double buffering or MemDC.
have found this article: Flicker-free-drawing-of-any-control
This article explains it well how to quickly perform Non Flickering drawing on your CListCtrl.
And it works excellent.
PS: VS 2005 doesn't have CMemDC class you will need to implement it your self, or use the following code:
//
// CMemDC.h header file
//
#pragma once
class CMemDC
{
public:
CMemDC(CDC& dc, CWnd* pWnd);
CMemDC(CDC& dc, const CRect& rect);
virtual ~CMemDC();
CDC& GetDC() { return m_bMemDC ? m_dcMem : m_dc; }
BOOL IsMemDC() const { return m_bMemDC; }
BOOL IsVistaDC() const { return m_hBufferedPaint != NULL; }
void EraseBkClip();
protected:
CDC& m_dc;
BOOL m_bMemDC;
HANDLE m_hBufferedPaint;
CDC m_dcMem;
CBitmap m_bmp;
CBitmap* m_pOldBmp;
CRect m_rect;
};
//
// CMemDC.cpp source file
//
#include "CMemDC.h"
CMemDC::CMemDC(CDC& dc, CWnd* pWnd) :
m_dc(dc), m_bMemDC(FALSE), m_hBufferedPaint(NULL), m_pOldBmp(NULL)
{
ASSERT_VALID(pWnd);
pWnd->GetClientRect(m_rect);
m_rect.right += pWnd->GetScrollPos(SB_HORZ);
m_rect.bottom += pWnd->GetScrollPos(SB_VERT);
if (m_dcMem.CreateCompatibleDC(&m_dc) &&
m_bmp.CreateCompatibleBitmap(&m_dc, m_rect.Width(), m_rect.Height()))
{
m_bMemDC = TRUE;
m_pOldBmp = m_dcMem.SelectObject(&m_bmp);
}
}
CMemDC::CMemDC(CDC& dc, const CRect& rect) :
m_dc(dc), m_bMemDC(FALSE), m_hBufferedPaint(NULL), m_pOldBmp(NULL), m_rect(rect)
{
ASSERT(!m_rect.IsRectEmpty());
if (m_dcMem.CreateCompatibleDC(&m_dc) &&
m_bmp.CreateCompatibleBitmap(&m_dc, m_rect.Width(), m_rect.Height()))
{
m_bMemDC = TRUE;
m_pOldBmp = m_dcMem.SelectObject(&m_bmp);
}
}
CMemDC::~CMemDC()
{
if (m_bMemDC)
{
CRect rectClip;
int nClipType = m_dc.GetClipBox(rectClip);
if (nClipType != NULLREGION)
{
if (nClipType != SIMPLEREGION)
{
rectClip = m_rect;
}
m_dc.BitBlt(rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height(), &m_dcMem, rectClip.left, rectClip.top, SRCCOPY);
}
m_dcMem.SelectObject(m_pOldBmp);
}
}
void CMemDC::EraseBkClip()
{
CRect clip;
m_dcMem.GetClipBox(&clip);
m_dcMem.FillSolidRect(clip, GetSysColor(COLOR_WINDOW));
}
There is an ultra simple way I found that worked for me:
Turn off redraw with m_List1.SetRedraw(false)
Reset contents with m_List1.ResetContents()
Add new strings in loop with m_List1.AddString()
Then finalize by turning back on redraw and a m_List1.UpdateWindow().