博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
sudoku me_Sudoku,一个完整的MFC应用程序。 第6部分
阅读量:2530 次
发布时间:2019-05-11

本文共 15818 字,大约阅读时间需要 52 分钟。

sudoku me

介绍: (Introduction:)

网格按钮的所有者绘制。 单例类的实现和用法。

Continuing from the fifth article about sudoku.  

续上关于数独的第五篇文章。

Open the project in visual studio.

在Visual Studio中打开项目。

Go to the class view – CGridButton should be visible as a class.  Right click and go to the properties.  As in the first article click the button to display the overrides, scroll down to PreSubclassWindow and add a function in the combo box.  The editor window should have GridButton.cpp opened and the following code there:

转到类视图– CGridButton应该作为一个类可见。 右键单击并转到属性。 与第一篇文章一样,单击按钮以显示替代,向下滚动到PreSubclassWindow并在组合框中添加一个函数。 编辑器窗口应打开GridButton.cpp,并在其中显示以下代码:

void CGridButton::PreSubclassWindow(){
// TODO: Add your specialized code here and/or call the base class CButton::PreSubclassWindow();}

Change it to the following

将其更改为以下内容

void CGridButton::PreSubclassWindow(){
ModifyStyle(NULL, BS_OWNERDRAW);}

Notice we don’t need to call the base class implementation (if you looked at it you find it does nothing).  

注意,我们不需要调用基类实现(如果您看过它,则发现它什么也不做)。

A quick aside.  Leave the call to the base class there, put the cursor on that line and press the F9 key.  You should see a red dot to the far left of that line.  Now compile and run and the program should stop executing with a yellow arrow now in the red button.  Press the F11 key (step into) and you see the code in that function – does nothing it says so there.

The ModifyStyle – the first parameter is styles to be removed, we don’t want any removing, the second is styles to add.  We want an owner draw button so we add the style flag to indicate owner draw – BS_OWNERDRAW.

ModifyStyle –第一个参数是要删除的样式,我们不需要任何删除,第二个参数是要添加的样式。 我们需要一个所有者绘制按钮,因此我们添加样式标记以指示所有者绘制– BS_OWNERDRAW。

An owner draw crash of the application, a simple debug exercise.  The program now compiles but will crash in running – try it.  Sudoku.exe has triggered a breakpoint.  Now press the continue button.  The message appears again but on screen another file is displayed – I have winctrl1.cpp.  Now press the break button on the message box.  You should see a yellow arrow pointing at a line ASSERT(FALSE);  This is the complete code

所有者绘制应用程序崩溃,这是一个简单的调试练习。 该程序现在可以编译,但是在运行时会崩溃–试试。 Sudoku.exe已触发断点。 现在按继续按钮。 该消息再次出现,但在屏幕上显示另一个文件–我有winctrl1.cpp。 现在按消息框上的中断按钮。 您应该看到一个黄色箭头指向ASSERT(FALSE)行; 这是完整的代码

// Derived class is responsible for implementing all of these handlers//   for owner/self draw controlsvoid CButton::DrawItem(LPDRAWITEMSTRUCT){
ASSERT(FALSE);}

The source of the crash is located and the comment gives us a pretty strong hint of what we are doing wrong.  Namely the button is now owner drawn but we have not done any code to draw it.  

崩溃的原因已找到,并且注释为我们提供了我们做错了什么的强烈暗示。 即按钮现在是所有者绘制的,但我们尚未完成任何代码绘制。

Now repeat the steps for adding the PreSubclassWindow but this time select and add a DrawItem handler.  (It will now compile and run without error but we see no buttons – owner draw means exactly that, we must now draw everything – colours, fonts, dynamic pictures – the display is now ours).

现在重复添加PreSubclassWindow的步骤,但是这次选择并添加一个DrawItem处理程序。 (它现在可以编译并运行,没有错误,但是我们看不到任何按钮–所有者绘制就意味着,我们现在必须绘制所有内容–颜色,字体,动态图片–现在是我们的显示)。

This application is only going to use a fairly simple display of the button – none of the themed effects that would be possible.  To display the text of the button we will require a font, some sort of border would be useful and a visual hint as feedback that the button has the focus.  

该应用程序仅将使用按钮的相当简单的显示-没有可能的主题效果。 为了显示按钮的文本,我们将需要一种字体,某种边框会很有用,并且视觉提示会反馈按钮具有焦点。

For the font it could be possible to create it everytime the button needs to be redrawn.  Does that slow things down?  If we had the font as a member variable of the CGridButton class then it would have to be created once.  However we have 81 instances of that class – so 81 individual copies of the font each taking a limited resource up.  (Each would have a ‘HANDLE’ and there is a limited number of these available to the operating system, admittedly a lot more than were available on the 16 bit versions of windows).  Can we just make one copy of the font and share it?  One solution would be to have it as a member of the application, here we will develop an alternative – a singleton class.

对于字体,可以在每次需要重绘按钮时创建它。 这会减慢速度吗? 如果将字体作为CGridButton类的成员变量,则必须创建一次。 但是,我们有该类的81个实例–因此,该字体的81个单独副本各自占用了有限的资源。 (每个都有一个“ HANDLE”,操作系统中可用的数量有限,诚然比16位版本的Windows上可用的数量要多得多)。 我们可以只复制一个字体并共享吗? 一种解决方案是让它成为应用程序的成员,在这里我们将开发一个替代方案– singleton类

From the ‘solution’ view right click Sudoku and choose Add then Class from the context menu.  From the Wizard choose C++ in the tree (we used MFC previously for the button, but there isn’t a singleton base class in MFC), C++ class from the installed templates at the right, then click the Add button.  For the class name enter CUIElements (User Interface Elements) then click Finish.

在“解决方案”视图中,右键单击Sudoku,然后从上下文菜单中选择“添加”,然后选择“类”。 从向导中,在树中选择C ++(我们以前使用MFC作为按钮,但是MFC中没有单例基类),从右侧已安装的模板中选择C ++类,然后单击“添加”按钮。 对于类名称,输入CUIElements(用户界面元素),然后单击Finish。

In the code window should be the file UIElements.h with the following contents:

在代码窗口中应该是文件UIElements.h,其内容如下:

#pragma onceclass CUIElements{
public: CUIElements(void); ~CUIElements(void);};

This is going to be a simple singleton class not designed to cope with multi threading issues.  The code should be modified so it is now this:

这将是一个简单的单例类,不适用于解决多线程问题。 该代码应该被修改,所以现在是这样:

class CUIElements{
private: CUIElements() {}; CUIElements(const CUIElements&) {}; CUIElements& operator=(const CUIElements&) { return Instance(); }; ~CUIElements() {}; CFont m_fntNumber;public: static CUIElements& Instance(); const CFont& GetNumberFont();};

This is an implementation of a Meyers (Scott Meyers) singleton.  

这是Meyers(Scott Meyers)单例的实现。

Notice first the constructor is private, as are copy constructor and assignment operators, even the destructor.  This prevents them from being called outside the class and from derving a new class based on this.  What an odd looking class this is.  How can we create an object of this sort?  

请注意,首先,构造函数是私有的,复制构造函数和赋值运算符甚至是析构函数也是私有的。 这样可以防止在类之外调用它们,并防止以此为基础推导新的类。 这是一个多么奇怪的类。 我们如何创建这种对象?

The clue is in the public members, namely the static member function called Instance – that returns a reference to an object of this sort.  A reference is preferable to a pointer - one can attempt to delete a pointer for instance.  Notice we are really trying to make certain only one instance can be created external to the class and that it can't be destroyed externally.

线索在于公共成员,即称为Instance的静态成员函数,该函数返回对此类对象的引用。 引用比指针更可取-例如,可以尝试删除指针。 注意,我们实际上是在尝试确保只能在类外部创建一个实例,并且不能在外部将其销毁。

Now open the UIElements.cpp file and add the following code there:

现在打开UIElements.cpp文件,并在其中添加以下代码:

CUIElements& CUIElements::Instance(){
//static means it is not to be destroyed once the function exits, to be shared across all instances of this class static CUIElements inst; return inst;}const CFont& CUIElements::GetNumberFont() { if(m_fntNumber.GetSafeHandle() == NULL) {
//font not yet created - create it now //Obtain a DC (Device Context) for the screen HDC hDC = ::GetDC(NULL); int iLogPixelsY = GetDeviceCaps(hDC, LOGPIXELSY); ::ReleaseDC(NULL, hDC); LOGFONT logFont; memset( &logFont, 0, sizeof(logFont)); //24 point bold, arial font _tcscpy_s(logFont.lfFaceName, LF_FACESIZE, _T("Arial")); logFont.lfHeight = -MulDiv(24, iLogPixelsY, 72); logFont.lfWeight = FW_BOLD; logFont.lfUnderline = false; logFont.lfItalic = false; m_fntNumber.CreateFontIndirect(&logFont); } //Safety - check it has been created ASSERT(m_fntNumber.GetSafeHandle()); return m_fntNumber; }

First we see the static member function ‘Instance’ has a static member variable – ahhh it is a CUIElement.  Now a static member is shared across all instances of a class (not local to one object), also it is not destroyed when the function exits.  

首先,我们看到静态成员函数'Instance'具有一个静态成员变量–啊,它是一个CUIElement。 现在,静态成员在类的所有实例之间共享(不是在一个对象本地),并且在函数退出时也不会被破坏。

As mentioned this is not a thread safe technique but it is satisfactory for our application and simple to code.  

如前所述,这不是线程安全的技术,但对我们的应用程序来说令人满意,并且易于编写代码。

Why is it not thread safe?  In theory one could have two threads which require the singleton object.  The very first call to the Instance function is not thread safe because the first thread through the path creates the object. If the second thread then tries to go through the Instance function at the same time there is a race condition as to which will create the instance object and it is possible that one of them may slip through and return a reference to the instance before it has actually been created.

There are some methods to stop this race condition happening, a simple one is to make sure it is called at least once in the main thread before another thread could access it. This will ensure the instance object is created before multiple threads try to access the object. The function itself is completely thread safe after the initial call.

有一些方法可以阻止这种竞争情况的发生,一种简单的方法是确保在主线程中至少一次调用它,然后另一个线程可以访问它。 这将确保在多个线程尝试访问该对象之前创建实例对象。 初始调用后,函数本身是完全线程安全的。

We also have a member variable which is a CFont type of variable.  Typical of many MFC objects it has a two stage creation.  If the Handle associated with it is NULL then it is not created completely – test it and create it if required.  We require information about the number of pixels for an inch of screen real estate, this we use in determining the height of the font.  We first fill in a LOGFONT structure with the information about the font we require then we create the font indirectly based on this information.  (Note the app assumes this will succeed – we really ought to have code to return a font if our custom font creation fails).

我们还有一个成员变量,它是CFont类型的变量。 在许多MFC对象中,它都有两个阶段创建。 如果与之关联的Handle为NULL,则说明它不是完全创建的-请对其进行测试并根据需要创建。 我们需要有关一英寸屏幕不动产的像素数的信息,这用于确定字体的高度。 我们首先使用有关所需字体的信息填充LOGFONT结构,然后根据此信息间接创建字体。 (请注意,应用程序假定此操作将会成功–如果自定义字体创建失败,我们确实应该具有返回字体的代码)。

As there is only ever one instance of the singleton then this font is only created once.

因为只有一个单例实例,所以此字体仅创建一次。

Back to the button:  We must draw everything so we need to draw the frame and contents of the button.  A visual hint that the button is focussed is important (else how can the user know which grid element is active).  Maybe one should also display the ‘locked’ buttons.  We will also display an ‘invalid’ number as highlighted (eg. 7 appears twice in a row – not a correct solution)

返回按钮:我们必须绘制所有内容,因此我们需要绘制按钮的框架和内容。 重要的是视觉上提示按钮已聚焦(否则用户如何知道哪个网格元素处于活动状态)。 也许还应该显示“锁定”按钮。 我们还将以突出显示的方式显示“无效”数字(例如,“ 7”连续出现两次-不是正确的解决方案)

Now modify the DrawItem code so it is as follows:

现在修改DrawItem代码,如下所示:

void CGridButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct){
ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON); //Get an instance of the singleton - for the font CUIElements& instUI = CUIElements::Instance(); //Get the button text - if any to display CString szText; if(GetValue() > 0) szText.Format("%d", GetValue()); //Obtain an MFC device context - less typing for the next steps CDC dc; dc.Attach(lpDrawItemStruct->hDC); //Perform a save of the state, we will be manipulating it, this allows one line of code to restore the state later int iSaveDC = dc.SaveDC(); //Draw a simple rectangle, if button has the focus then draw another just inside the first CRect rc(lpDrawItemStruct->rcItem); if (lpDrawItemStruct->itemState & ODS_FOCUS) {
dc.Rectangle(&rc); rc.DeflateRect(1, 1); dc.Rectangle(&rc); } else dc.Rectangle(rc.left, rc.top, rc.right, rc.bottom); //if there is a number to display then we display it if(GetValue() > 0) {
//Display a locked cell in a different colour if(m_bLock) {
rc.DeflateRect(1, 1); dc.FillSolidRect(&rc, m_clrLockedCell); } dc.SelectObject(instUI.GetNumberFont()); // Draw the button text using the text color blue (or red - if invalid and grid is filled) COLORREF clr = (m_bValid ? RGB(0,0,255) : RGB(255,0,0)); dc.SetTextColor(clr); //If locked then we need to set the background colour - else it looks ghastly if(m_bLock) dc.SetBkColor(m_clrLockedCell); dc.DrawText(szText, &rc, DT_SINGLELINE|DT_VCENTER|DT_CENTER); } else {
//Draw hint } //restore the state of the dc to the original dc.RestoreDC(iSaveDC); //We don't require the dc any more, release it dc.Detach();}

We need a couple of lines to be added to the header file (GridButton.h).

我们需要将几行添加到头文件(GridButton.h)。

private:    bool m_bValid;    static const COLORREF m_clrLockedCell;public:    void SetValid(bool bFlag) { m_bValid = bFlag; };

This declares two variables we used in the DrawItem and also a new function for setting the validity state of the number entered into the GridButton.

这声明了我们在DrawItem中使用的两个变量,以及一个用于设置输入到GridButton中的数字的有效性状态的新函数。

In the GridButton.cpp file we need to add an #include “UIElements.h” to the top of the file like this:

在GridButton.cpp文件中,我们需要在文件顶部添加#include“ UIElements.h”,如下所示:

#include "GridButton.h"#include "UIElements.h"const COLORREF CGridButton::m_clrLockedCell = RGB(192, 192, 192);

The static variable for the colour of a locked cell is done as shown in the previous line of code (Note it is not in the constructor because this is a const shared over all instances of the CGridButton class).

锁定单元格颜色的静态变量如上一行代码所示完成(注意,它不在构造函数中,因为这是CGridButton类的所有实例共享的const)。

To the initialisation list at the constructor we set the variable controlling the validity to a default value of true – it should appear as follows:

对于构造函数的初始化列表,我们将控制有效性的变量设置为默认值true –它应如下所示:

,    m_bLock(false),    m_bValid(true)

Finally we need to make certain that this ‘valid’ flag is reset to true when we load a game, so in the CSudokuView::OnUpdate we need an extra line of code as we load the grid contents:

最后,我们需要确保在加载游戏时将此“有效”标志重置为true,因此在CSudokuView :: OnUpdate中,加载网格内容时需要额外的一行代码:

m_arWndButtons[row * 9 + col].SetLock();                m_arWndButtons[row * 9 + col].SetValid(true);

Now you can press F5 to compile and run.  Load the previously saved game and see how it now looks and behaves.  

现在,您可以按F5进行编译和运行。 加载先前保存的游戏,然后查看其外观和行为。

结论: (Conclusion:)

We have coded owner drawing of a window object (button).

我们已经编码了一个窗口对象(按钮)的所有者图。

We have implemented a singleton class to provide one copy of a font that can be used anywhere in the application.

我们已经实现了一个singleton类,以提供一种字体的副本,该副本可以在应用程序中的任何位置使用。

Click here for the source code for this article
单击此处获取本文的源代码

Previous article in the series is here:  

该系列中的上一篇文章在这里:

There we used the PreTranslateMessage function to provide keyboard support to navigate the grid.  We also implemented keyboard entry of the numbers when on a cell in the grid.

在那里,我们使用了PreTranslateMessage函数来提供键盘支持来导航网格。 当在网格中的单元格上时,我们还实现了数字键盘输入。

Next article in the series is here:  

该系列的下一篇文章在这里:

Here we will be implementing hints to help in solving a game.  We will also meet a class nested inside another and some simple debugging to find and cure a bug in the code.

在这里,我们将实施一些提示以帮助解决游戏。 我们还将遇到嵌套在另一个类中的一个类,以及一些简单的调试程序,以查找和解决代码中的错误。

Two points to bear in mind.

需要牢记两点。

You may use the code but you are not allowed to distribute the resulting application either for free or for a reward (monetary or otherwise).  At least not without my express permission.

您可以使用代码,但不能免费或以奖励(货币或其他方式)分发结果应用程序。 至少没有我的明确许可。

I will perform some things to demonstrate a point – it is not to be taken as that meaning it is a ‘best’ practice, in fact an alternative might be simpler and suitable.  Some points in the code would even be called poor design and a possible source of errors.

我将做一些事情来说明一个观点–不能认为它是“最佳”实践,实际上,另一种选择可能更简单,更合适。 代码中的某些要点甚至被称为不良设计和可能的错误源。

翻译自:

sudoku me

转载地址:http://sgqzd.baihongyu.com/

你可能感兴趣的文章
AccountManager教程
查看>>
Android学习笔记(十一)——从意图返回结果
查看>>
算法导论笔记(四)算法分析常用符号
查看>>
ultraedit激活
查看>>
总结(6)--- python基础知识点小结(细全)
查看>>
亿级曝光品牌视频的幕后设定
查看>>
ARPA
查看>>
JSP开发模式
查看>>
我的Android进阶之旅------>Android嵌入图像InsetDrawable的使用方法
查看>>
Detours信息泄漏漏洞
查看>>
win32使用拖放文件
查看>>
Android 动态显示和隐藏软键盘
查看>>
raid5什么意思?怎样做raid5?raid5 几块硬盘?
查看>>
【转】how can i build fast
查看>>
null?对象?异常?到底应该如何返回错误信息
查看>>
django登录验证码操作
查看>>
(简单)华为Nova青春 WAS-AL00的USB调试模式在哪里开启的流程
查看>>
图论知识,博客
查看>>
[原创]一篇无关技术的小日记(仅作暂存)
查看>>
20145303刘俊谦 Exp7 网络欺诈技术防范
查看>>