MFC CScrollView does not clear background - c++

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.

Related

Drawing a chess board with SDL2's SDL_RenderDrawRect

I am fairly new to SDL2 and as my first project I wanted to just create a chess board. This has proven to be harder than I thought.
I have tried lots of different ways to draw the fields of the chess board with SDL_RenderDrawRect, this is the current state:
#include <SDL2/SDL.h>
int main()
{
bool quit = false;
SDL_Event event;
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow("Chess", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, 680, 680, 0);
SDL_Renderer *render = SDL_CreateRenderer(window, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
for (int x = 0; x > 3; x++) {
for (int y = 0; x > 8; x++) {
SDL_Rect rect;
rect.x = x*10;
rect.y = y*10;
rect.w = 128;
rect.h = 128;
SDL_SetRenderDrawColor(render, 159, 84, 8, 255);
SDL_RenderFillRect(render, &rect);
}
}
SDL_RenderPresent(render);
while (!quit) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit = true;
break;
}
}
SDL_Quit();
return 0;
}
No rectangles want to show up when I use SDL_RenderDrawRect in a loop. Any ideas why that is?
Cheers!
There are multiple errors in your for loops.
You have to change your conditions in your for loops. Both iteration variables(x and y) start with 0, but your condition to run the foor loop is that they are greater than 3 or 8, so they wont get executed. Change it to less than the value(< instead of >)
You have to change the iteration variable of your second for loop. You create y but the condition and the value change is for x
Your Rect is 128x128 big, but you multiply your x and y from your loops by 10, that means your Rectangles will overlap. You have to multiply it by at least 128.
If you look at a Chess Board, beginning at the top left and going from left to right, every second field is colored. To implement that you have to start in the top left corner, and iterate over every cell, and then jump to the next row. Every second field, you have to draw a Rect.
This is a slightly edited version of your algorithm:
int startPos = 0;
for (int y = 0; y < 8; y++) {
for (int x = startPos; x < 3; x+=2) {
SDL_Rect rect;
rect.x = x * 129;
rect.y = y * 129;
rect.w = 128;
rect.h = 128;
SDL_SetRenderDrawColor(render, 159, 84, 8, 255);
SDL_RenderFillRect(render, &rect);
}
startPos = 1 - startPos;
}
Explanation:
In the first row, the first field will be colored, because startPos is 0.
Since every row has the opposite start of its predecessor, we have to change startPos as soon as the last cell of a row is drawn.
Since we only need to draw every second cell, the x has to be increased by two every iteration
Hope this helped you a little bit
Although ur problem is solved, leaving this for someone who face such issue
(anyhow its quite easy to make the board, I wrote this code)
this will create a standard 8x8 chess board
void Create_Board() {
SDL_Rect Box = { 0,0,80,80}; COORD position = { 0 };
bool toggle_color = 0;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
if (toggle_color)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
else
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderFillRect(renderer, &Box);
Box.x += 80;
if(j != 7) //needed for chess color pattern
toggle_color = !toggle_color;
}
Box.x = 0;
Box.y += 80;
}
}

How do I get maximum centered rectangular area of Image in firemonkey c++

I'm trying to crop an image in a rectangular area, to fill a circle with it, but I cannot find any way to do that. Tt says that the result of my code is not correct and the image is not well placed. I don't know what is wrong in my code.
I saw some examples , but they are not working for me.
Here are the sites I visited:
crop and align inserted BMP in Delphi
https://www.experts-exchange.com/questions/22142653/Crop-Bitmap-Image1.html
Delphi - how do I crop a bitmap "in place"?
How to crop an FMX TBitmap
Note: the code should be wrote in FMX whit C++, it should run in all platforms.
The TCircle have the WrapMode assign to TileStrech.
void __fastcall TForm9::Button1Click(TObject *Sender)
{
int X , Y , W , H;
if(Image1->Bitmap->Width >= Image1->Bitmap->Height)
{
H = Image1->Bitmap->Height;
X = (Image1->Bitmap->Width - Image1->Bitmap->Height) / 2;
Y = 0;
W = H;
}
else
{
W = Image1->Bitmap->Width;
X = 0;
Y = (Image1->Bitmap->Width - Image1->Bitmap->Height) / 2;
H = W;
}
TRect rect(X, Y, W, H);
TBitmap * bm = new TBitmap(W,H);
bm->CopyFromBitmap(Image1->Bitmap ,rect ,X,Y);
Circle1->Fill->Bitmap->Bitmap->Assign(bm);
Image2->Bitmap->Assign(bm);
delete bm;
}
The result of this code is:
The bitmap is not well placed in a Circle
My objetive is get an crop image like this :
image croped
Solved
I have one solution for this problem
int CropBitmap(TBitmap* in, TBitmap* out)
{
if (!in or !out)
{
return 1;
}
TBitmap * bm = new TBitmap();
int origH = in->Height;
int origW = in->Width;
TRect rect;
int defH, defW;
if (origH > origW)
{
bm->Height = origW;
bm->Width = origW;
int factor = (origH - origW) / 2;
rect.Top = factor;
rect.Left = 0;
rect.Right = origW;
rect.Bottom = factor + origW;
bm->CopyFromBitmap(in, rect, 0, 0);
}
else if (origW > origH)
{
bm->Height = origH;
bm->Width = origH;
int factor = (origW - origH) / 2;
rect.Top = 0;
rect.Left = factor;
rect.Right = factor + origH;
rect.Bottom = origH;
bm->CopyFromBitmap(in, rect, 0, 0);
}
else
{
bm->Assign(in);
}
out->Assign(bm);
delete bm;
return 0;
}
It run well for me.

Why CDC::LineTo() doesn't draw in Visual C++ 2015 MFC Dialog?

I'm learning MFC and I'm trying to draw some lines on a MFC Dialog-based application main window, it shall be a rather simple task but while running I see no lines drawing on the dialog.
Following is the method I wrote:
// draw corner of a rectangle on specified device context
void CTestDrawCornerDlg::DrawCorner(
CDC* pDC,
const CornerType& type,
const CPoint& position,
const unsigned int& size
)
{
CPen pen(PS_SOLID, 5, RGB(0, 0, 0));
CPen* pOldPen = pDC->SelectObject(&pen);
CPoint pH, pV;
// I could make following lines simply with a 2-lines block,
// but I'd leave it as it was to make it easier to understand.
switch (type)
{
case LEFT_TOP:
pH.x = position.x + size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y + size;
break;
case LEFT_BOTTOM:
pH.x = position.x - size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y + size;
break;
case RIGHT_TOP:
pH.x = position.x + size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y - size;
break;
case RIGHT_BOTTOM:
pH.x = position.x - size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y - size;
break;
default: break;
}
pDC->MoveTo(position);
pDC->LineTo(pH);
pDC->MoveTo(position);
pDC->LineTo(pV);
pDC->SelectObject(pOldPen);
}
And I called this method in OnPaint method of Dialog class:
void CTestDrawCornerDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
// lines generated automatically when creating
// MFC project are truncated for brevity
}
else
{
CDialogEx::OnPaint();
}
CPaintDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
I guess it's a newbie mistake but I just don't know what the mistake is. Thanks for help!
P.S. please download from following link the MFC project to re-create this problem:
https://www.dropbox.com/s/exeehci9kopvgsn/TestDrawCorner.zip?dl=0
You can change your code to use CDialogEx::OnPaint() + CClientDC as follows:
void CTestDrawCornerDlg::OnPaint()
{
CDialogEx::OnPaint();
CClientDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
or just use CPaintDC:
void CTestDrawCornerDlg::OnPaint()
{
CPaintDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
But don't use OnPaint + CPaintDC
To see the problem, note how OnPaint and CPaintDC are defined in MFC:
void CDialog::OnPaint()
{
CPaintDC dc(this);
if (PaintWindowlessControls(&dc))
return;
Default();
}
CPaintDC::CPaintDC(CWnd* pWnd)
{
if (!Attach(::BeginPaint(m_hWnd = pWnd->m_hWnd, &m_ps)))
AfxThrowResourceException();
}
::BeginPaint is a core WinAPI function. It should only be called once in response to WM_PAINT, and it can't be used anywhere else.
CClientDC on the other hand uses ::GetDC which can be used pretty much anywhere, as long as window handle is available.

How do I scale a rectangle to another rectangle (such as a picture to a window) preserving aspect ratio with the option to fill?

I'm putting this here because the algorithm for doing this is more difficult to find than it should be. Hopefully Google will cache this.
The problem is: you have a bitmap and a window. You want to draw the bitmap inside a window, filling the window, keeping the aspect ratio, as the window resizes.
You may also want to be able to fit it the other way, so that you can draw the image "over" the window, and all the area in the window will be filled. This will clip out some of the image. I present in the answer a simple algorithm for doing so.
Here's an implementation that uses integer math only.
The algorithm first stretches both dimensions, preserving aspect ratio. The new size is calculated, assuming that the respective other dimension occupies the entire space. Of these new dimensions, the one that overshoots the available area is set to the maximum possible value, while the other is scaled back, preserving aspect ratio. (For pan and scan (bScale is set to true) mode, the dimension that doesn't overshoot the available space is set to occupy the entire range.)
(Note: If sizePicture is an empty rectangle, this function returns a rectangle that stretches one pixel to the left and one pixel up, either from the origin, or the center.)
RECT size_rect( RECT& rcScreen,
RECT& sizePicture,
bool bCenter/*,
bool bScale*/ ) {
int clientWidth = rcScreen.right - rcScreen.left;
int clientHeight = rcScreen.bottom - rcScreen.top;
int picWidth = sizePicture.right - sizePicture.left;
int picHeight = sizePicture.bottom - sizePicture.top;
// Calculate new content size
int contentWidth = ::MulDiv( clientHeight, picWidth, picHeight );
int contentHeight = ::MulDiv( clientWidth, picHeight, picWidth );
// Adjust dimensions to fit inside client area
if ( contentWidth > clientWidth ) {
// To use the bScale parameter that allows the image to fill the entire
// client area, use the following if-clause instead.
//if ( ( bScale && ( contentWidth < clientWidth ) )
// || ( !bScale && ( contentWidth > clientWidth ) ) ) {
contentWidth = clientWidth;
contentHeight = ::MulDiv( contentWidth, picHeight, picWidth );
} else {
contentHeight = clientHeight;
contentWidth = ::MulDiv( contentHeight, picWidth, picHeight );
}
RECT rect = { 0 };
::SetRect( &rect, 0, 0, contentWidth, contentHeight );
if ( bCenter ) {
// Calculate offsets to center content
int offsetX = ( clientWidth - contentWidth ) / 2;
int offsetY = ( clientHeight - contentHeight ) / 2;
::OffsetRect( &rect, offsetX, offsetY );
}
return rect;
}
Make two RECT. One is the window you wish to fit to (passed into rcScreen), and the other holds the dimensions of the picture:
(pseudo-code)
RECT window;
GetClientRect(hwnd,&window)
RECT bitmap_rect;
BITMAP bitmap;
bitmap_rect.left = bitmap_rect.top = 0;
bitmap_rect.right = bitmap.bmWidth;
bitmap_rect.bottom = bitmap.bmHeight;
RECT draw_rect = size_rect(window,bitmap_rect,true,true);
Then StretchBlt it:
StretchBlt(toDC, draw_rect.left, draw_rect.top, draw_rect.right, draw_rect.bottom, fromDC, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
This is the function: (note there is no case for bCenter = false and Scale = true). **bCenter is flag for "center picture in window." Scale is flag for "pan and scan mode" instead of "letterbox," useful if you are using an image as a window background that you want resized but don't want to have letterboxes. **
RECT size_rect(RECT& rcScreen,
RECT& sizePicture,
bool bCenter,
bool Scale)
{
RECT rect = rcScreen;
double dWidth = rcScreen.right - rcScreen.left;
double dHeight = rcScreen.bottom - rcScreen.top;
double dAspectRatio = dWidth / dHeight;
double dPictureWidth = sizePicture.right - sizePicture.left;
double dPictureHeight = sizePicture.bottom - sizePicture.top;
double dPictureAspectRatio = dPictureWidth / dPictureHeight;
double nNewHeight = dHeight;
double nNewWidth = dWidth;
double nHeightCenteringFactor = 0;
double nWidthCenteringFactor = 0;
double xstart = rcScreen.left;
double ystart = rcScreen.top;
if (dPictureAspectRatio > dAspectRatio)
{
if (bCenter && Scale) {
nNewWidth = dPictureWidth*(1 / (dPictureHeight / dHeight));
xstart = rcScreen.left - ((nNewWidth / 2) - (dWidth / 2));
}
else {
nNewHeight = (int)(dWidth / dPictureWidth*dPictureHeight);
if (bCenter)
ystart = ((dHeight - nNewHeight) / 2) + rcScreen.top;
}
}
else if (dPictureAspectRatio < dAspectRatio)
{
if (bCenter && Scale) {
nNewHeight = dPictureHeight*(1 / (dPictureWidth / dWidth));
ystart = rcScreen.top - ((nNewHeight / 2) - (dHeight / 2));
}
else{
nNewWidth = (dHeight / dPictureHeight*dPictureWidth);
if (bCenter)
xstart = ((dWidth - nNewWidth) / 2) + rcScreen.left;
}
}
SetRect(&rect, xstart, ystart, nNewWidth, nNewHeight);
return rect;
}

Better control of my client area(WIN32)

I am trying to make a 3 x 3 grid with a black pen on a window. however I want it to be centered, for example that my grid is inside a white space,
10% of top, right, left and bottom. and my grid will fit in the remaining 80% even when we resize the window.
Now I could make the grid but after several attempts to create the 10% area, got frustrated.
case WM_SIZE:
//get the 10% range.
cxInvalid = LOWORD(lParam) * 0.1;
cyInvalid = HIWORD(lParam) * 0.1;
//get the grid, DIVISIONS = 3
cxBlock = LOWORD(lParam) / DIVISIONS;
cyBlock = HIWORD(lParam) / DIVISIONS;
return 0;
Thanks in advaced :)
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
for (x = 0; x < DIVISIONS; x++)
for (y = 0; y < DIVISIONS; y++)
{
Rectangle(hdc, x * cxBlock, y * cyBlock,
(x + 1) * cxBlock, (y + 1) * cyBlock);
}
EndPaint(hwnd, &ps);
return 0;
This is exactly the sort of problem for which Windows mapping modes are intended to be used. For the moment I'm going to assume that you want your grid to remain square, regardless of the shape of the window it's in.
One way to do that is to switch from the default MM_TEXT mapping mode to the MM_ISOTROPIC mapping mode (but if we want the grid to change shape with the surrounding window, we'd use MM_ANISOTRCOPIC instead).
Using that, we can set our window as a virtual grid of, say, 1200 x 1200 cells, and then draw our 3x3 grid on that. I've chosen 1200 x 1200 so the part we care about will be a nice, convenient 1000 x 1000 grid.
// set up the mapping mode:
RECT rect;
GetClientRect(hWnd, &rect);
SetMapMode(hDC, MM_ISOTROPIC);
SetViewportExt(rect.x, rect.y);
// The virtual width/height for our window:
static const int width = 1200;
static const int height = 1200;
SetWindowExt(width, height);
SetWindowOrg(-100, -100); // Set the virtual 0 point ~10% of the way into the window.
// And then draw the grid. We always draw in a 1000 x 1000 grid, and Windows
// scales that to the actual window size for us.
//
static const int grid_size = 1000;
static const int step = grid_size / 3;
for (int i = step; i < grid_size-1; i += step) {
MoveTo(hDC, i, 0);
LineTo(hDC, i, grid_size);
MoveTo(hDC, 0, i);
LineTo(hDC, grid_size, i);
}
To reiterate the difference between MM_ISOTROPIC and MM_ANISOTROPIC, here are screen shots of the grid. First as it's drawn with MM_ISOTROPIC:
...and then as it's drawn with MM_ANISOTROPIC: