CScrollView only scrolls through parts of a large area - c++

I encountered a problem scrolling large areas in a CScrollView. When slowly moving the scrollbar from top to bottom the behaviour is the following: At first the scrolling is working fine. At some point scrolling further does not do anything, instead the top of the area is shown. At some point scrolling starts again from the top.
Here is a small example. I created a new MFC project using the document-view-model and used a CScrollView as the view class. I added the following code to create a large area and add some text to show, which part is currently shown:
void CScrollViewTest2View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal;
// TODO: calculate the total size of this view
sizeTotal.cx = sizeTotal.cy = 100*1000;
SetScrollSizes(MM_TEXT, sizeTotal);
for(int i = 0; i < 1000; i++)
{
CStatic* label = new CStatic();
label->Create(NULL, WS_CHILD | WS_VISIBLE, CRect(10,10 + i*100,100,30 + i*100), this);
CString text;
text.Format(L"%d",i);
label->SetWindowText(text);
}
}
If I add the following code I see that during the scrolling the 'nPos' value seems to wrap around. This would explain the behaviour. But I don't know how to work around that.
BOOL CScrollViewTest2View::OnScroll(UINT nScrollCode, UINT nPos, BOOL bDoScroll )
{
CString msg;
msg.Format(L"nPos = %u\n",nPos);
TRACE(msg);
return CScrollView::OnScroll(nScrollCode, nPos, bDoScroll);
}
and here is the output when it stops scrolling:
nPos = 27826
nPos = 29190
nPos = 30281
nPos = 31372
nPos = 31645
nPos = 32464
nPos = 4294938588
nPos = 4294939134
nPos = 4294939407
nPos = 4294939680
nPos = 4294940225
nPos = 4294940771
So is there a way to use a CScrollView to completely scroll down a large area?
The code of the sample project can be found here.

The static controls are not placed where you expect them to be. To see the problem, run the following code:
static CStatic test;
CRect r(0, 0, 100, 30);
r.MoveToY(40000);
test.Create(0, WS_CHILD | WS_VISIBLE, r, this);
test.GetWindowRect(r);
TRACE("%d\n", r.top);
r.top should be 32767. This is because of 16bit limits in Windows. All of the controls whose x/y position exceed this limit, are pushed back to this position.
OnScroll function suffers from a similar problem, however this can be fixed by using GetScrollInfo
Add ON_WM_VSCROLL to message map and the following OnVScroll override to your class
void CScrollViewTest2View::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CView::OnVScroll(nSBCode, nPos, pScrollBar);
SCROLLINFO info;
GetScrollInfo(SB_VERT, &info, SIF_ALL);
int pos = info.nPos;
int save = pos;
switch (nSBCode)
{
case SB_LEFT: pos = info.nMin; break;
case SB_RIGHT: pos = info.nMax; break;
case SB_LINELEFT: pos--; break;
case SB_LINERIGHT: pos++; break;
case SB_PAGELEFT: pos -= info.nPage; break;
case SB_PAGERIGHT: pos += info.nPage; break;
case SB_THUMBPOSITION: pos = info.nTrackPos; break;
case SB_THUMBTRACK: pos = info.nTrackPos; break;
}
//make sure the new position is within range
if (pos < info.nMin) pos = info.nMin;
int max = info.nMax - info.nPage + 1;
if (pos > max) pos = max;
OnScrollBy(CSize(0, pos - save), 1);
//EDIT: moved this line after OnScrollBy and added condition
if (info.nPos != pos)
{
info.nPos = pos;
SetScrollInfo(SB_VERT, &info, FALSE);
}
UpdateWindow();
}
This will not solve the problem with windows controls whose x/y position is greater than 32,000, but at least it will scroll the DC as expected.
For testing, remove the static controls. You can use the draw function below to test the painting:
void CScrollViewTest2View::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(0, 100 * 1000);
SetScrollSizes(MM_TEXT, sizeTotal);
}
void CScrollViewTest2View::OnDraw(CDC* pDC)
{
for (int i = 0; i < 1000; i++)
{
int y = i * 100;
CString s;
s.Format(L"%d ", y);
pDC->TextOutW(200, y, s);
}
}

Related

DrawText Refresh Problem - Text Disappears

I'm using Objective Grid and wanted to have a grid cell control that can show an icon and text. So I took RogueWave's example (https://rwkbp.makekb.com/entry/466/) and modified it as below.
Initially the text renders correctly, but if I do anything in the grid that refreshes the relevant cell, the text disappears. If I update the whole grid, or even refresh the cell by dragging it off screen and back again, the text reappears - until it disappears again. Seems like a paint or InvalidateRect problem - but I can't figure out how to fix it. Any ideas?
Thanks!
//------------------------------------------------------------------------------
void CGXIconControl::Draw(CDC* pDC, CRect rect, ROWCOL nRow, ROWCOL nCol, const CGXStyle& style, const CGXStyle* pStandardStyle)
//------------------------------------------------------------------------------
{
BOOL b;
ASSERT(pDC != NULL && pDC->IsKindOf(RUNTIME_CLASS(CDC)));
// ASSERTION-> Invalid Device Context ->END
ASSERT(nRow <= Grid()->GetRowCount() && nCol <= Grid()->GetColCount());
// ASSERTION-> Cell coordinates out of range ->END
ASSERT_VALID(pDC);
DrawBackground(pDC, rect, style);
pDC->SetBkMode(TRANSPARENT);
if (rect.right <= rect.left || rect.Width() <= 1 || rect.Height() <= 1)
{
return;
}
CString str = style.GetIncludeValue() ? style.GetValue() : _T("");
CString sTxtBefore = _T("");
CString sTxtAfter = _T("");
int nHAlign = style.GetHorizontalAlignment();
int nVAlign = style.GetVerticalAlignment();
// Save these value to restore them when done drawing
COLORREF crOldTextColor = pDC->GetTextColor();
COLORREF crOldBkColor = pDC->GetBkColor();
if (!style.GetEnabled())
{
pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT));
}
CBrush Brush;
Brush.CreateSolidBrush(style.GetEnabled() ? ::GetSysColor(COLOR_WINDOWTEXT) : ::GetSysColor(COLOR_GRAYTEXT));
LOGBRUSH lBrush = { 0 };
Brush.GetLogBrush(&lBrush);
CBrush* pOldBrush = (CBrush*)pDC->SelectStockObject(NULL_BRUSH);
CPen linePen;
linePen.CreatePen(PS_SOLID, 1, &lBrush);
CPen* pOldPen = pDC->SelectObject(&linePen);
// Set font bold if necessary
CFont* pCurfont = pDC->GetCurrentFont();
LOGFONT lf;
CFont font;
if (pCurfont)
{
pCurfont->GetLogFont(&lf);
}
if (style.GetFont().GetBold())
{
lf.lfWeight = FW_BOLD;
}
font.CreateFontIndirect(&lf);
CFont* pOldFont = pDC->SelectObject(&font);
int nIcoStart = str.Find(_T("#ICO"));
if (nIcoStart == -1)
{
// We didn't find an icon indicator, so just draw the text
pDC->DrawText(str, rect, DT_SINGLELINE|nHAlign|nVAlign);
}
else
{
sTxtBefore = str.Left(nIcoStart);
CSize szBefore = pDC->GetTextExtent(sTxtBefore);
int nIconEnd = str.Find(_T(")"), nIcoStart);
CString strIDResource = str.Mid(nIcoStart + 5, nIconEnd - (nIcoStart + 5));
UINT nIDResource = _ttoi(strIDResource);
// Load the highest bit-depth available
HICON hIcon = NULL;
hIcon = LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 32);
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 24) : NULL;
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 16) : NULL;
hIcon == NULL ? LoadResourceIcon(nIDResource, m_nIconSize, m_nIconSize, 8) : NULL;
sTxtAfter = str.Right(str.GetLength() - nIconEnd - 1);
CSize szAfter = pDC->GetTextExtent(sTxtAfter);
CRect rectCell = CGXControl::GetCellRect(nRow, nCol, rect, &style);
CRect rectBefore = rectCell;
CRect rectAfter = rectCell;
int nTotalWidth = szBefore.cx + m_nIconSize + szAfter.cx;
// Calculate positions
int nTop, nLeft;
switch (nHAlign)
{
case DT_LEFT:
{
rectBefore.right = rectBefore.left + szBefore.cx;
nLeft = rectBefore.right;
rectAfter.left = nLeft + m_nIconSize;
rectAfter.right = rectAfter.left + szAfter.cx;
} break;
case DT_CENTER:
{
rectBefore.left = (rectCell.right - rectCell.Width() / 2) - nTotalWidth / 2;
rectBefore.right = rectBefore.left + szBefore.cx;
nLeft = rectBefore.right;
rectAfter.left = nLeft + m_nIconSize;
rectAfter.right = rectAfter.left + szAfter.cx;
} break;
case DT_RIGHT:
{
// Work from the right
rectAfter.right = rectCell.right;
rectAfter.left = rectAfter.right - szAfter.cx;
nLeft = rectAfter.left - m_nIconSize;
rectBefore.right = nLeft;
rectBefore.left = rectBefore.right - szBefore.cx;
} break;
}
switch (nVAlign)
{
case DT_TOP:
nTop = rectCell.top;
break;
case DT_VCENTER:
nTop = rectCell.top + (rectCell.Height() / 2 - m_nIconSize / 2);
break;
case DT_BOTTOM:
nTop = rectCell.bottom - m_nIconSize;
break;
}
if (!sTxtBefore.IsEmpty())
{
pDC->DrawText(sTxtBefore, rectBefore, DT_SINGLELINE|nHAlign|nVAlign);
}
b = ::DrawIconEx(pDC->m_hDC, nLeft, nTop, hIcon, m_nIconSize, m_nIconSize, 0, NULL, DI_NORMAL);
b = ::DestroyIcon(hIcon);
if (!sTxtAfter.IsEmpty())
{
pDC->DrawText(sTxtAfter, rectAfter, DT_SINGLELINE|nHAlign|nVAlign);
}
}
// Reset original values
pDC->SetTextColor(crOldTextColor);
pDC->SetBkColor(crOldBkColor);
pDC->SelectObject(pOldBrush);
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldFont);
// Child Controls: spin-buttons, hotspot, combobox btn, ...
CGXControl::Draw(pDC, rect, nRow, nCol, style, pStandardStyle);
}

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;

What is the correct way to use CFontDialog common dialog?

I recently change the relationship between the window extent and the view port of an MFC application, and, since then, every time I change my application font size, the selected font become supper gigantic even if I chose the smallest of all font size.
(EDIT: I noticed that CFontDialog::GetSize() return a size that is ten times the size chosen in the dialog. Is that a usual behaviour ? If not what could make the dialogue return such a value? Although I am not sure, but it seem this multiplication of size seem to be my problem. How do I get CFontDialog::GetSize() to return the actual selected size, if that indeed is the problem?)
What am I doing wrong? What is the correct way to use CFontDialog ?
Shown next is a snippet of the font changing code:
CClientDC dc(pView);
pView->OnPrepareDC(&dc)
pLastFont = pLastText->GetFont();
oldColor = pLastText->GetColor();
LOGFONT logFont = (LOGFONT) (*pLastFont);
CFontDialog fontDialog(&logFont);
CSize szPrevSize;
//Some missing codes
MyFigure *pMyFigure;
if(dynamic_cast<MyTextFigure*>(pMyFigure) != NULL)
{
if(dynamic_cast<MyTextBoxFigure*>(pLastText) != NULL)
{
MyTextBoxFigure *pTextBox = dynamic_cast<MyTextBoxFigure*>(pLastText);
szPrevSize = pTextBox->GetTextSize();
}
}
else if(dynamic_cast<MyTableFigure*>(pMyFigure) != NULL)
{
MyTableFigure *pTableFigure = dynamic_cast<MyTableFigure*>(pMyFigure);
TCell *pCell = (TCell *)*pTableFigure;
szPrevSize = pCell->GetTextSize();
}
fontDialog.m_cf.rgbColors = (COLORREF) oldColor;
if (fontDialog.DoModal() == IDOK)
{
fontDialog.GetCurrentFont(&logFont);
MyFont newFont = (MyFont) logFont;
MyColor newColor = (MyColor) fontDialog.GetColor();
pMyFigure->SetFont(newFont, &dc);
pMyFigure->SetColor(newColor);
}
Please note that MyFont is a wrapper around the LOGFONT structure and has an operator that enable cast to LOGFONT. Also note that MyFigure class has a MyFont data member that is set by the member function SetFont.
The following code shows how the relationship between the view port and the window extent was set.
void CDisplayView::OnInitialUpdate()
{
CRect rcClient;
GetClientRect(&rcClient);
CClientDC dc(this);
dc.SetMapMode(MM_ISOTROPIC);
CSize szWindow(m_pAppDoc->GetZoomRatio() * SCALE_RATIO * dc.GetDeviceCaps(HORZRES),m_pAppDoc->GetZoomRatio() * SCALE_RATIO * dc.GetDeviceCaps(VERTRES));
CSize szViewport(dc.GetDeviceCaps(HORZRES),dc.GetDeviceCaps(VERTRES));
dc.SetWindowExt(szWindow);
dc.SetViewportExt(szViewport);
dc.DPtoLP(&rcClient);
//And so on
}
ZoomRatio is 1, an SCALE_RATIO is 1.2
The assignment operator is:
MyFont& MyFont::operator=(const MyFont& font)
{
if (this != &font)
{
m_logFont = font.m_logFont;
}
return *this;
}
P.S.
The summary of the code is:
LOGFONT OldlogFont ;
LOGFONT newLogFont;
COLLORREF OldColorref;
COLORREF newColorref;
CFontDialog fontDialog(&OldlogFont);
fontDialog.m_cf.rgbColors = oldColorref;
if (fontDialog.DoModal() == IDOK)
{
fontDialog.GetCurrentFont(&OldlogFont);
newLogFont = OldlogFont;
newColorref = fontDialog.GetColor();
}
The focus here is the LOGFONT structure.
The user selected value is gotten with old logfont then assigned to new logfont.
For debugging purposes, I retrieved the size of the selected font and was shocked to find that it is ten times the size that I selected after lunching the CFontDialog for use in my application i.e
LOGFONT OldlogFont ;
LOGFONT newLogFont;
COLLORREF OldColorref;
COLORREF newColorref;
CFontDialog fontDialog(&OldlogFont);
fontDialog.m_cf.rgbColors = oldColorref;
if (fontDialog.DoModal() == IDOK)
{
fontDialog.GetCurrentFont(&OldlogFont);
newLogFont = OldlogFont;
newColorref = fontDialog.GetColor();
int iFontSize = fontDialog.GetSize();
//when I selected a font size of 10 from the dialog, what I get here in my code is 100.
}
This is how I do it:
void CExportSettingsDlg::OnBtnSelectFont()
{
CClientDC dc(this);
m_plfCurrentFont->lfHeight =
-MulDiv(m_plfCurrentFont->lfHeight, dc.GetDeviceCaps(LOGPIXELSY), 72);
CMyFontDialog dlgFont( m_plfCurrentFont );
dlgFont.m_cf.Flags |= CF_NOSCRIPTSEL;
dlgFont.m_cf.rgbColors = m_crCurrentFontColour;
if( dlgFont.DoModal() == IDOK)
{
m_plfCurrentFont->lfHeight = -(dlgFont.GetSize()/10);
// We must retrieve the font colour into the correct variable.
// The logfont is already correct because we passed in the
// logfont pointer into the font dialogue.
switch( m_iFontType )
{
case FONT_TITLE:
m_sSettings.crTitleFontColour = dlgFont.GetColor();
break;
case FONT_HEADING:
m_sSettings.crHeadingFontColour = dlgFont.GetColor();
break;
case FONT_GENERAL:
m_sSettings.crGeneralFontColour = dlgFont.GetColor();
break;
case FONT_EVENT:
m_sSettings.crEventFontColour = dlgFont.GetColor();
break;
case FONT_NOTES:
m_sSettings.crNotesFontColour = dlgFont.GetColor();
break;
}
// Update display
SetSampleFont();
// Html Preview
UpdatePreviewHtml();
}
}
If it does not answer your question I will remove the answer. Notice how I set the font size at the start of the code.
If you read up on the MSDN for GetSize it states:
The font's size, in tenths of a point

Has anyone ever used the "scroll arrows" in the horizontal scroll bar to scroll the positions?

I wish to use the scroll arrows present at both the ends of the scroll bar, to scroll the positions of the bar. As it is known that the scroll bars don't have the notifications as other controls have, so I am facing a problem.
My code for the scroll bar is as follows-
void CScrollBarExDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
if(nSBCode==SB_THUMBPOSITION)
{
if(pScrollBar==&m_Scroll)
{
m_Edit=nPos;
m_Scroll.SetScrollPos(nPos);
}
}
UpdateData(FALSE);
CDialogEx::OnHScroll(nSBCode, nPos, pScrollBar);
}
You have to modify the MSDN example slightly to work with a scroller control:
//add to message map
ON_WM_HSCROLL()
//initialize in OnInitDialog
SCROLLINFO info = { sizeof(SCROLLINFO) };
info.nMin = 0;
info.nMax = 100;
info.nPage = 1;
info.fMask = SIF_ALL;
m_Scroll.SetScrollInfo(&info, TRUE);
If info.nPage is greater than 1, it may throw off the range. Add this line to fix it:
info.nMax += info.nPage - 1;
Add overload:
void CMyDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
if (pScrollBar == &m_Scroll)
{
//get scrollbar information
SCROLLINFO info;
m_Scroll.GetScrollInfo(&info, SIF_ALL);
int pos = info.nPos;
//calculate the new position of scroll box
switch (nSBCode)
{
case SB_LEFT: pos = info.nMin; break;
case SB_RIGHT: pos = info.nMax; break;
case SB_LINELEFT: pos--; break;
case SB_LINERIGHT: pos++; break;
case SB_PAGELEFT: pos -= info.nPage; break;
case SB_PAGERIGHT: pos += info.nPage; break;
case SB_THUMBPOSITION: pos = nPos; break;
case SB_THUMBTRACK: pos = nPos; break;
}
//make sure the new position is within range
if (pos < info.nMin) pos = info.nMin;
//adjust the max value, incase we had changed it earlier in OnInitDialog
int max = info.nMax - info.nPage + 1;
if (pos > max) pos = max;
//set the new position
m_Scroll.SetScrollPos(pos);
}
}

Inconsistent Behaviour Between 2 Players of Game

I've been making a game for an English presentation (I know right?) and I've been having a few weird problems recently.
At this stage, there are two squares for the players that can fire bullets. When the bullets hit the side of the screen or the middle border, they should disappear and possibly be reused later.
The problem is, first of all, when either player shoots up or down, the bullets disappear into the top/bottom/middle of the screen like they're supposed to. If player 2 shoots a few to the side, as soon as one hits, they seem to all disappear (only player 2's bullets) and I have to wait a few seconds before it starts shooting again.
The main problem though, is that even when I use the exact same code modified for player 1 instead of player 2, when player 1's first shot to hit the side of the screen gets there, it segfaults the program.
The REALLY odd thing is that at one point, this happened, and without changing anything, I ran it again and it all worked perfectly fine. It might have to do with the order of bullets between player 1 and 2, or even where I shoot first. I've tried recreating it, but no results yet.
Just a note before you try to compile the code, I used a wrapper I made to make the window with ease. I noted down what goes on behind the scenes with /// comments though, so adding that info into whatever method you use to make your windows will work just as well.
Problem areas are listed near the bottom:
///Works best on 1280x1024 resolution
///1 vs 1 splitscreen game that involves flying around and shooting things
///angles start at 0 facing upwards and increase clockwise
#include <window.h> //incomplete wrapper, but works perfectly for quick, easy window creation
#define _USE_MATH_DEFINES //for M_PI
#include <cmath> //for M_PI
#include <iostream> //used for debugging
using std::cout; //output
struct Actions //actions a player can take
{
bool up; //if player is moving in these 4 directions, they will be true
bool left;
bool down;
bool right;
bool shoot; //if player is shooting, this will be true
};
struct Player //a player
{
Player() {};
void fire(); //fire a bullet
void checkActions (HWND); //check what actions player is taking
double x; //position (centre of square)
double y;
double angle; //angle (might add diagonals so...)
int pnum; //player number (0 or 1)
COLORREF colour; //player's colour
Actions action; //player's actions
};
struct Bullet //a bullet
{
double x; //position (centre of square)
double y;
Player owner; //owner of bullet
int index; //bullet's index in array
double angle; //bullet's angle
};
Player *p = new Player[2]; //2 players
Bullet **bullet; //2d array of bullets
int bcount[2] = {0}; //number of bullets for each player
int btime [2] = {0}; //timer for bullets
const double PLSIZE = 10; //player size = 20x20 square (10 from centre outwards)
const double BSIZE = 2; //bullet size = 4x4 square
const double SPEED = 1; //player's moving speed is 1
const int BDELAY = 100; //delay between bullets is 100ms
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //window procedure
void OnPaint (HDC, HWND); //painting function
void moveBullets (HWND); //calculates bullet positions
void deleteBullet (int, int); //"deletes" a bullet
int main() //main function
{
//hide(console()); //hides console window (currently showing for debugging)
bullet = new Bullet*[2]; //create bullet array of 1000/player (I'll size down the 1000 later)
bullet[0] = new Bullet[1000];
bullet[1] = new Bullet[1000];
p[0].x = 630; //player 1's position
p[0].y = 250;
p[0].colour = RGB(255,0,0); //player 1 is red
p[0].pnum = 0; //player 1's number is 0
p[0].angle = 0; //face upwards
p[0].action = {0}; //player 1 is doing nothing
p[1].x = 630; //player 2's position
p[1].y = 750;
p[1].colour = RGB(0,0,255); //player 2 is blue
p[1].pnum = 1; //player 2's number is 1
p[1].angle = 0; //face upwards
p[1].action = {0}; //player 2 is doing nothing
Window window; //create window object (part of wrapper, sets default values for class and window)
///background = (HBRUSH)COLOR_WINDOW
///class name = "Default Wrapper Class"
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
window.createClass(WndProc); //create class using earlier-mentioned window procedure
window.setStyle(WS_OVERLAPPEDWINDOW | WS_MAXIMIZE); //set window style to overlapped and maximized
window.setTitle (L"Word Blaster"); //set window title to "Word Blaster" (it's an English project, shush)
///x/y/width/height = CW_USEDEFAULT
///class name = other class name
///hInstance = GetModuleHandle (NULL)
///all others are standard default or 0
HWND hwnd = window.createWindow(); //create window
MSG msg; //message loop
while(GetMessage(&msg,0,0,0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) //window proc
{
HDC hdc; //hdc for painting
PAINTSTRUCT ps; //paintstruct for painting
bool ret = false; //return value (you'll see later)
switch(msg)
{
case WM_CREATE: //currently not in use
break;
case WM_KEYDOWN: //check for pressed keys
switch (wParam) //keycode
{
case 0x57: //'w'
p[0].action.up = true; //player 1 wants to move up (couldn't just change position here or no diagonal movement)
break;
case 0x41: //'a', left
p[0].action.left = true;
break;
case 0x53: //'s', down
p[0].action.down = true;
break;
case 0x44: //'d', right
p[0].action.right = true;
break;
case 0x20: // space, shoot
p[0].action.shoot = true;
break;
case VK_UP: //up arrow, player 2 up
p[1].action.up = true;
break;
case VK_LEFT: //left arrow
p[1].action.left = true;
break;
case VK_DOWN: //down arrow
p[1].action.down = true;
break;
case VK_RIGHT: //right arrow
p[1].action.right = true;
break;
case VK_RETURN: //either enter key, p2 shoot
p[1].action.shoot = true;
break;
}
break;
case WM_KEYUP: //check for unpressed keys
switch (wParam)
{
case 0x57: //'w', player 1 should stop moving up
p[0].action.up = false;
break;
case 0x41: //all same order as above
p[0].action.left = false;
break;
case 0x53:
p[0].action.down = false;
break;
case 0x44:
p[0].action.right = false;
break;
case 0x20: // space
p[0].action.shoot = false;
break;
case VK_UP:
p[1].action.up = false;
break;
case VK_LEFT:
p[1].action.left = false;
break;
case VK_DOWN:
p[1].action.down = false;
break;
case VK_RIGHT:
p[1].action.right = false;
break;
case VK_RETURN:
p[1].action.shoot = false;
break;
}
break;
case WM_PAINT: //draw on screen
hdc = BeginPaint (hwnd, &ps); //prepare window for drawing
OnPaint (hdc,hwnd); //draw
EndPaint (hwnd, &ps); //finish drawing
break;
case WM_CLOSE: //if ready to close
show(console()); //show console window in case it doesn't close
end(); //close console window (PostMessage (GetConsoleWindow(),WM_CLOSE,0,0))
DestroyWindow(hwnd); //close main window
break;
case WM_DESTROY: //window is closing
PostQuitMessage(0); //post WM_QUIT to end (probably won't get here since console closes earlier)
break;
case WM_ERASEBKGND: //if background is going to be erased, don't let it (causes flicker)
ret = true; //hold that thought for a bit
break;
}
p[0].checkActions(hwnd); //check player 1's actions
p[1].checkActions(hwnd); //check player 2's actions
moveBullets (hwnd); //move any bullets
InvalidateRect (hwnd,NULL,true); //update window
Sleep (1); //delay a bit
if (!ret) return DefWindowProc(hwnd, msg, wParam, lParam); //if WM_ERASEBKGND wasn't called, take default action
}
void Player::fire() //fire a bullet
{
bullet [pnum][bcount[pnum]].x = x; //bullet starts in player's centre
bullet [pnum][bcount[pnum]].y = y;
bullet [pnum][bcount[pnum]].owner = *this; //owner of bullet is the object calling this function
bullet [pnum][bcount[pnum]].index = bcount[pnum]; //index of bullet is the number of bullets for player
bullet [pnum][bcount[pnum]].angle = angle; //angle of bullet is player's angle
while (
(bullet[pnum][bcount[pnum]].x - BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x - BSIZE > x - PLSIZE //left side of bullet inside player OR
|| bullet[pnum][bcount[pnum]].x + BSIZE < x + PLSIZE && bullet[pnum][bcount[pnum]].x + BSIZE > x - PLSIZE) //right side in player --- AND ---
&& (bullet[pnum][bcount[pnum]].y - BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y - BSIZE > y - PLSIZE //top in player OR
|| bullet[pnum][bcount[pnum]].y + BSIZE < y + PLSIZE && bullet[pnum][bcount[pnum]].y + BSIZE > y - PLSIZE) //bottom in player
)
{
bullet[pnum][bcount[pnum]].x += sin (bullet[pnum][bcount[pnum]].angle * M_PI / 180); //start moving bullet until it's out
bullet[pnum][bcount[pnum]].y -= cos (bullet[pnum][bcount[pnum]].angle * M_PI / 180);
}
btime [pnum] = GetTickCount(); //set up bullet delay for that player
++bcount[pnum]; //increase number of bullets for that player
}
void Player::checkActions (HWND hwnd) //check player's actions
{
RECT r;
GetClientRect (hwnd, &r); //get canvas space
if (action.up) //if moving up
{
y -= SPEED; //change y position
angle = 0; //change angle
if (pnum == 0) //if player 1
{
if (y - PLSIZE < 1) y = PLSIZE + 1; //check top of screen boundary
}
else //if player 2
{
if (y - PLSIZE < r.bottom / 2 + 5) y = r.bottom / 2 + 5 + PLSIZE; //check middle boundary
}
}
if (action.left) //if moving left
{
x -= SPEED; //change x position
angle = 270; //change angle
if (x - PLSIZE < 1) x = PLSIZE + 1; //check left of screen boundary
}
if (action.down) //down is opposite of up
{
y += SPEED;
angle = 180;
if (pnum == 0)
{
if (y + PLSIZE > r.bottom / 2 - 5) y = r.bottom / 2 - 5 - PLSIZE;
}
else
{
if (y + PLSIZE > r.bottom) y = r.bottom - PLSIZE;
}
}
if (action.right) //right is opposite of left
{
x += SPEED;
angle = 90;
if (x + PLSIZE > r.right) x = r.right - PLSIZE;
}
if (action.shoot && GetTickCount() - btime [pnum] > BDELAY) fire(); //if player wants to shoot and enough time has passed, fire bullet
}
void OnPaint (HDC hdc, HWND hwnd) //draw stuff
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
HDC buffer = CreateCompatibleDC (hdc); //create buffer DC
HBITMAP bitmap = CreateCompatibleBitmap (hdc,r.right,r.bottom); //create buffer bitmap
HBITMAP oldBM = (HBITMAP)SelectObject (buffer, bitmap); //create another bitmap
HBRUSH player1brush = CreateSolidBrush(p[0].colour); //player 1's brush
HBRUSH player2brush = CreateSolidBrush(p[1].colour); //player 2's brush
HBRUSH blackBrush = CreateSolidBrush (RGB(0,0,0)); //black brush
HPEN /*player1*/pen = CreatePen (PS_NULL,1,RGB(255,0,0)); //don't need pen
BitBlt(buffer,0,0,r.right,r.bottom,NULL,0,0,WHITENESS); //erase bitmap background
SelectObject(buffer,pen); //select pen (since I need one to do anything)
SelectObject (buffer, blackBrush); //select black brush
Rectangle (buffer, 0, r.bottom / 2 - 5, r.right, r.bottom / 2 + 5); //draw middle line
// MoveTo () //these comments are because I was about to change the graphics to ships
SelectObject (buffer,player1brush); //select player 1's brush
Rectangle (buffer,p[0].x-PLSIZE,p[0].y-PLSIZE,p[0].x+PLSIZE,p[0].y+PLSIZE); //draw player 1
SelectObject (buffer,player2brush); //do the same for p2
Rectangle (buffer,p[1].x-PLSIZE,p[1].y-PLSIZE,p[1].x+PLSIZE,p[1].y+PLSIZE);
if (bcount[0] > 0) //if p1 has a bullet
{
SelectObject (buffer, blackBrush); //select black brush
for (int i = 0; i < bcount[0]; ++i) //draw bullet(s)
{
Ellipse (buffer, bullet [0][i].x - BSIZE, bullet [0][i].y - BSIZE, bullet [0][i].x + BSIZE, bullet [0][i].y + BSIZE);
}
}
if (bcount[1] > 0) //same goes for p2
{
SelectObject (buffer, blackBrush);
for (int i = 0; i < bcount[1]; ++i)
{
Ellipse (buffer, bullet [1][i].x - BSIZE, bullet [1][i].y - BSIZE, bullet [1][i].x + BSIZE, bullet [1][i].y + BSIZE);
}
}
BitBlt(hdc, 0,0, r.right , r.bottom, buffer, 0,0, SRCCOPY); //copy buffer bitmap to window
DeleteObject (player1brush); //delete stuff
DeleteObject (player2brush);
DeleteObject (pen);
SelectObject (buffer, oldBM);
DeleteObject (bitmap);
DeleteDC(buffer);
}
void moveBullets (HWND hwnd) //move the bullets ***PROBLEM AREA***
{
RECT r;
GetClientRect (hwnd, &r); //get canvas area
if (bcount[0] > 0) //if p1 has bullet(s)
{
for (int i = 0; i < bcount[0]; ++i) //go through p1's bullets
{
///DOESN'T WORK
bullet [0][i].x += sin (bullet [0][i].angle * M_PI / 180); //move the bullet horizontally
if (bullet [0][i].x - BSIZE < 1 || bullet [0][i].x + BSIZE > r.right) //if out of bounds
{
deleteBullet (0, bullet [0][i].index); //delete the bullet
--i; //if bullet [2] was deleted, bullet [2] will now be the old bullet [3] so recheck this one next time
}
///WORKS PERFECTLY
bullet [0][i].y -= cos (bullet [0][i].angle * M_PI / 180); //do same for y, including middle border
if (bullet [0][i].y - BSIZE < 1 || bullet [0][i].y + BSIZE > r.bottom / 2 - 5)
{
deleteBullet (0, bullet [0][i].index);
--i;
}
}
}
if (bcount[1] > 0) //exact same thing (I checked a LOT) for p2
{
for (int i = 0; i < bcount[1]; ++i)
{
///WORKS PERFECTLY (at least in the p1 sense, there is a slight problem)
bullet [1][i].x += sin (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].x - BSIZE < 1 || bullet [1][i].x + BSIZE > r.right)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
///WORKS PERFECTLY
bullet [1][i].y -= cos (bullet [1][i].angle * M_PI / 180);
if (bullet [1][i].y - BSIZE < r.bottom / 2 + 5 || bullet [1][i].y + BSIZE > r.bottom)
{
deleteBullet (1, bullet [1][i].index);
--i;
}
}
}
}
void deleteBullet (int player, int index) //delete bullet ***PROBLEM AREA***
{
if (index != bcount [player] - 1) //if it isn't the last bullet
{
for (int j = index; j < bcount[player] - 1; ++j) //go from here to the end of the current bullets - 1
{
bullet [player][j] = bullet [player][j+1]; //copy the next bullet into this spot
--bullet [player][j].index; //change the index of the bullet since it was moved back one
}
}
--bcount [player]; //lessen the bullet count, this is all that's needed if it's the last bullet
}
Any help with said problems or with something else you notice would be greatly appreciated.
EDIT: one side thing I forgot to ask was if there was a better way to continuously do things like move bullets for example than to put all that outside of the switch in the window procedure and the DefWindowProc after it.
It looks like deleteBullet may get called twice on the same i inside the loop for (i=0...) in moveBullets. I think this could happen if the bullet is moving diagonally. I'm not sure it's the cause of the trouble, though.
First English question I've seen on stackoverflow!