视图: 画出你的游戏界面
前面,我们的文档对象中已经初始化了游戏板对象,接下来我们需要显示这些信息给用户了。
第一步是添加代码,来重新设置我们的窗口尺寸。缺省的窗口尺寸不是我们想要的,我们将重写OnInitialUpdate 方法来实现这一点。视图类继承了一个缺省的OnInitialUpdate方法,我们希望重写它来重定义我们窗口的尺寸。OnInitialUpdate方法在客户区被初始化更新的时候调用。首先我们来看一下如何添加该方法。
切换到类视图,选中CSameGameView,然后按Alt+Enter
点击重写图标,如下图找到OnInitialUpdate方法进行重写:
以下是修改后的视图类头文件,注意字体着重的部分是修改的地方。
#pragma once class CSameGameView : public CView { protected: // create from serialization only CSameGameView(); DECLARE_DYNCREATE(CSameGameView) // Attributes public: CSameGameDoc* GetDocument() const; // Overrides public: virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: // Implementation public: void ResizeWindow(); virtual ~CSameGameView(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif // Generated message map functions protected: DECLARE_MESSAGE_MAP() public: virtual void OnInitialUpdate(); }; #ifndef _DEBUG // debug version in SameGameView.cpp inline CSameGameDoc* CSameGameView::GetDocument() const { return reinterpret_cast<CSameGameDoc*>(m_pDocument); } #endif
在增加ResizeWindow方法的同时,我们也需要增加描绘游戏界面方法到CSameGameView类中。视图类的头文件和源文件中已经包含了一个 OnDraw方法,这里正是我们放置描绘界面代码的地方。以下是视图类实现类cpp的全部代码,注意着重字体的地方是新增的内容。
#include "stdafx.h" #include "SameGame.h" #include "SameGameDoc.h" #include "SameGameView.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CSameGameView IMPLEMENT_DYNCREATE(CSameGameView, CView) BEGIN_MESSAGE_MAP(CSameGameView, CView) END_MESSAGE_MAP() // CSameGameView construction/destruction CSameGameView::CSameGameView() { } CSameGameView::~CSameGameView() { } BOOL CSameGameView::PreCreateWindow(CREATESTRUCT& cs) { return CView::PreCreateWindow(cs); } // CSameGameView drawing void CSameGameView::OnDraw(CDC* pDC) // MFC will comment out the argument name by default; uncomment it { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Save the current state of the device context int nDCSave = pDC->SaveDC(); // Get the client rectangle CRect rcClient; GetClientRect(&rcClient); // Get the background color of the board COLORREF clr = pDoc->GetBoardSpace(-1, -1); // Draw the background first pDC->FillSolidRect(&rcClient, clr); // Create the brush for drawing CBrush br; br.CreateStockObject(HOLLOW_BRUSH); CBrush* pbrOld = pDC->SelectObject(&br); // Draw the squares for(int row = 0; row < pDoc->GetRows(); row++) { for(int col = 0; col < pDoc->GetColumns(); col++) { // Get the color for this board space clr = pDoc->GetBoardSpace(row, col); // Calculate the size and position of this space CRect rcBlock; rcBlock.top = row * pDoc->GetHeight(); rcBlock.left = col * pDoc->GetWidth(); rcBlock.right = rcBlock.left + pDoc->GetWidth(); rcBlock.bottom = rcBlock.top + pDoc->GetHeight(); // Fill in the block with the correct color pDC->FillSolidRect(&rcBlock, clr); // Draw the block outline pDC->Rectangle(&rcBlock); } } // Restore the device context settings pDC->RestoreDC(nDCSave); br.DeleteObject(); } // CSameGameView diagnostics #ifdef _DEBUG void CSameGameView::AssertValid() const { CView::AssertValid(); } void CSameGameView::Dump(CDumpContext& dc) const { CView::Dump(dc); } // non-debug version is inline CSameGameDoc* CSameGameView::GetDocument() const { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSameGameDoc))); return (CSameGameDoc*)m_pDocument; } #endif //_DEBUG void CSameGameView::OnInitialUpdate() { CView::OnInitialUpdate(); // Resize the window ResizeWindow(); } void CSameGameView::ResizeWindow() { // First get a pointer to the document CSameGameDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if(!pDoc) return; // Get the size of the client area and the window CRect rcClient, rcWindow; GetClientRect(&rcClient); GetParentFrame()->GetWindowRect(&rcWindow); // Calculate the difference int nWidthDiff = rcWindow.Width() - rcClient.Width(); int nHeightDiff = rcWindow.Height() - rcClient.Height(); // Change the window size based on the size of the game board rcWindow.right = rcWindow.left + pDoc->GetWidth() * pDoc->GetColumns() + nWidthDiff; rcWindow.bottom = rcWindow.top + pDoc->GetHeight() * pDoc->GetRows() + nHeightDiff; // The MoveWindow function resizes the frame window GetParentFrame()->MoveWindow(&rcWindow); }
描绘游戏板是非常简单的,就是一行一行一列一列的画出每一个彩色的砖块到屏幕上。
我们首先将客户区背景色填充成黑色,其中客户区填充颜色的取得通过呼叫GetBoardSpace(-1,-1) 方法;客户区大小的取得通过呼叫 GetClientRect方法,最后通过调用FillSolidRect方法实现填充任务。
// Get the client rectangle CRect rcClient; GetClientRect(&rcClient); // Get the background color of the board COLORREF clr = pDoc->GetBoardSpace(-1, -1); // Draw the background first pDC->FillSolidRect(&rcClient, clr);
接下来,我们来画一个一个的小砖块,我们要画一个带颜色的矩形和一个黑色的边框,为了实现这点我们将画刷的类型设置成HOLLOW_BRUSH,这样当我们调用Rectangle()方法时不会用默认的白色背景填充我们的小方块。
嵌套的for循环的逻辑是非常简单的,就是一行一行,一列一列的在客户区描绘出小方块。通过文档类,我们可以获得每一个小砖块块随机的颜色,我们还可以得到小砖块的大小,进而计算出每个小方块应该描绘的位置。我们通过FillSolidRect() 方法来填充小砖块的颜色。通过 Rectangle() 方法来画小砖块的边框。
// Draw the squares for(int row = 0; row < pDoc->GetRows(); row++) { for(int col = 0; col < pDoc->GetColumns(); col++) { // Get the color for this board space clr = pDoc->GetBoardSpace(row, col); // Calculate the size and position of this space CRect rcBlock; rcBlock.top = row * pDoc->GetHeight(); rcBlock.left = col * pDoc->GetWidth(); rcBlock.right = rcBlock.left + pDoc->GetWidth(); rcBlock.bottom = rcBlock.top + pDoc->GetHeight(); // Fill in the block with the correct color pDC->FillSolidRect(&rcBlock, clr); // Draw the block outline pDC->Rectangle(&rcBlock); } }
接下来,我们要根据我们描绘的小砖块的个数及大小,重新计算窗口的大小。
// Get the size of the client area and the window CRect rcClient, rcWindow; GetClientRect(&rcClient); GetParentFrame()->GetWindowRect(&rcWindow);
注意我们取得了2个窗口的尺寸,一个是框架窗口(包含客户区,菜单,工具栏,及状态栏),一个是客户区的大小。我们首先需要计算出框架窗口和客户区窗口的高度差,宽度差;然后再把这个高度差加上我们所有砖块的行高度得到我们最终的窗口的高度,宽度的计算同理。
// Calculate the difference int nWidthDiff = rcWindow.Width() - rcClient.Width(); int nHeightDiff = rcWindow.Height() - rcClient.Height(); // Change the window size based on the size of the game board rcWindow.right = rcWindow.left + pDoc->GetWidth() * pDoc->GetColumns() + nWidthDiff; rcWindow.bottom = rcWindow.top + pDoc->GetHeight() * pDoc->GetRows() + nHeightDiff; // The MoveWindow function resizes the frame window
最后我们调用父窗口也即框架窗口的MoveWindow方法重新设置窗口的大小。
GetParentFrame()->MoveWindow(&rcWindow);
最终,我们的程序看起来应该是这样的:
结论
在这篇文章里,我们首先回顾了MFC的基本知识和文档视图结构的基本概念。接下来,我们抽象了一个游戏板类型,其中包含了我们的游戏数据信息,并且构建了一个视图将游戏信息描绘到了界面中,下一章,我们将运用事件驱动编程方法,实现与用户的交互,比如鼠标点击事件,来实现一个可玩的游戏版本。