sudoku me

介绍: (Introduction:)

提示网格按钮。 嵌套类,模板化集合。 压扁那个臭虫!

Continuing from the sixth article about sudoku.

继续关于数独的第六篇文章。

Open the project in visual studio.

在Visual Studio中打开项目。

First we will finish with the SUD_SETVALUE message as this enables the validity checking (a tiny bit missing from the owner draw in the last article – the code in the owner draw was in place) and provides the base for the hinting mechanism.

首先,我们将以SUD_SETVALUE消息结束,因为这将启用有效性检查(上一篇文章的所有者图纸中缺少一点点-所有者图纸中的代码已经到位)并为提示机制提供了基础。

Sudoku requires that the numbers 1..9 be placed in a block of nine positions, each digit can only appear once.  The classic version we deal with here has each row as one of these blocks, each column and the 9x9 grid split into 9 3x3 grids, each of which also constitutes a block.  All of this we will be wrapping inside a Grid which will contain all 81 individual cells.  One cell can appear in many blocks.

数独要求将数字1..9放置在九个位置的块中,每个数字只能出现一次。 我们在这里处理的经典版本将每一行作为这些块之一,将每一列和9x9网格划分为9个3x3网格,每个网格也构成一个块。 所有这些我们都将包裹在一个网格中,该网格将包含所有81个单独的单元格。 一个单元格可以出现在许多块中。

(Specially shaped blocks and diagonals are not covered with this version of the application – maybe a later version would allow user to define their own area.)  Each of these ‘blocks’ should have the information about the nine positions internally and expose functionality to determine which numbers are or are not used within the block.  They can also return information about the validity of numbers entered – eg. 7 is used twice in a block, which is not a valid solution to sudoku.

(此版本的应用程序未涵盖特殊形状的块和对角线-也许更高版本将允许用户定义自己的区域。)每个“块”都应在内部具有有关九个位置的信息,并公开确定功能块中使用或不使用哪些数字。 他们还可以返回有关输入数字有效性的信息,例如。 7在一个块中使用了两次,这不是数独的有效解决方案。

We therefore need two new classes, a CGrid and a  CBlock – which contains information about the nine positions.  Inside each cell in the grid we need to keep information to identify uniquely the nine positions such as the CWnd* pointer.  Theoretically one could store a CGridButton* pointer and call the object directly to find the contents but that strongly binds the classes together.  It would be nice to be able to solve a sudoku game automatically in the background (multithreading whooo!!) and that doesn’t necessarily require the usage of a CGridButton.  So we are also going to store the value alongside the ID of the position.

因此,我们需要两个新类,一个CGrid和一个CBlock-包含有关九个位置的信息。 在网格的每个单元内,我们需要保留信息以唯一地标识九个位置,例如CWnd *指针。 从理论上讲,可以存储一个CGridButton *指针并直接调用该对象以查找内容,但这将这些类牢固地绑定在一起。 能够在后台自动解决数独游戏(多线程哇!)会很好,并且不一定需要使用CGridButton。 因此,我们还将将值与头寸ID一起存储。

We could use two arrays, so the array index ‘n’ in both arrays would be the ID and the associated value.  That doesn’t make me happy: it is an easy place to make a coding error and get the two arrays out of synchronisation.  It makes more sense to have a structure (or class) that contains all of this information and then in the CGrid a collection of these – no possibility of the ID and the corresponding value becoming detached from one another.  This pairing is of no use to another class so we will make it a nested structure.  By that I mean it will be declared inside the class declaration of the CGrid, we will also use a templated array collection to keep these object in.  Using a templated collection allows the compiler to check if it should be stored and when accessed to determine it is the correct sort of object being returned.  Further to this nesting the information about a block could be internal to the Grid – that is to be responsible for organising the data.

我们可以使用两个数组,因此两个数组中的数组索引“ n”将是ID和关联的值。 那并不令我高兴:这是一个容易发生编码错误并使两个数组脱离同步的容易位置。 具有包含所有这些信息的结构(或类),然后在CGrid中包含这些信息的集合(这样就更有意义)-ID和相应的值彼此分离的可能性不大。 这种配对对另一个类没有用,因此我们将其设为嵌套结构。 我的意思是,它将在CGrid的类声明中声明,我们还将使用模板化数组集合将这些对象保留在内部。使用模板化集合可使编译器检查是否应存储它以及何时对其进行访问以确定它是要返回的正确对象类型。 进一步嵌套之后,有关块的信息可能位于网格内部,即负责组织数据。

Now we need to add a new class to the project, the CGrid class.  This is just a normal C++ class, no base class.  We have done this before but for simplicity – solution explorer, right click on project Sudoku, choose Add> class.  From the wizard select C++ in the tree view, then C++ Class from the list at the top right.  Click on Add button, enter CGrid as the class name then click Finish.  Let’s prepare the view first.

现在,我们需要向项目添加一个新类CGrid类。 这只是普通的C ++类,没有基类。 我们之前已经做过,但是为了简单起见-解决方案资源管理器,右键单击Sudoku项目,选择“添加”>“类”。 从向导的树视图中选择C ++,然后从右上角的列表中选择C ++类。 单击添加按钮,输入CGrid作为类名,然后单击完成。 让我们先准备视图。

To the SudokuView.h file we need to add a #include for the grid.h file and add a new function for a custom message.

对于SudokuView.h文件,我们需要为grid.h文件添加#include并为自定义消息添加新功能。

#pragma once
#include "grid.h"
….
private:CGrid m_Grid;
protected:afx_msg LRESULT OnSetValue(WPARAM wParam, LPARAM lParam);

In the OnInitialUpdate we need to assign the buttons to the blocks

在OnInitialUpdate中,我们需要将按钮分配给块

    if(!m_bInitialised){…CURRENT CODE IS HERE…//Now create the data structure contentsfor(int iRow = 0; iRow < 9; iRow++){for(int iCol = 0; iCol < 9; iCol++){//The return value ought to be checked, this just assumes it worksm_Grid.AddCell(iRow, iCol, (UINT_PTR)&m_arWndButtons[iRow * 9 + iCol]);}}}

Note the CWnd* is cast to a UINT_PTR value otherwise there will be a compiler error.  For brevity we are not checking if the AddCell has succeeded but please remember that assuming things is poor practice.  This now links the display to the data.

请注意,CWnd *被强制转换为UINT_PTR值,否则将出现编译器错误。 为简便起见,我们不检查AddCell是否成功,但是请记住,假设情况不佳。 现在,这会将显示链接到数据。

To handle the custom message we need to add an entry into the MESSAGE_MAP – this allows the compiler to provide code linking the message to a function in the application.

为了处理自定义消息,我们需要在MESSAGE_MAP中添加一个条目-这允许编译器提供将消息链接到应用程序中的函数的代码。

BEGIN_MESSAGE_MAP(CSudokuView, CFormView)ON_MESSAGE(SUD_SETVALUE, &CSudokuView::OnSetValue)
END_MESSAGE_MAP()

And we need the body of the function also

我们还需要函数的主体

LRESULT CSudokuView::OnSetValue(WPARAM wParam, LPARAM lParam)
{//Update the grid, note casting the WPARAM and LPARAM into the type of values expected by the functionm_Grid.SetValue((UINT_PTR)wParam, (int)lParam);//Now flag each cell as having a valid entryfor(int i = 0; i < 81; i++)m_arWndButtons[i].SetValid(true);//Set a flag that the window should be repainted - WHEN NOT BUSYInvalidate();//If there is any cell with a zero then the game is not yet finished, no further validity checking requiredfor(int i = 0; i < 81; i++){if(m_arWndButtons[i].GetValue() == 0)return 1;}//All the cells contain a non zero value, is the game correctly finished?//We must check each button and set the internal flagfor(int i = 0; i < 81; i++){if(!m_Grid.CheckValid((UINT_PTR)&m_arWndButtons[i])m_arWndButtons[i].SetValid(false);}return 1;
}

Now for the coding of the grid and the internal data structures it contains.

现在用于网格及其包含的内部数据结构的编码。

class CGrid
{public:CGrid(void) {};~CGrid(void);bool AddCell(int iRow, int iCol, UINT_PTR uiID);void SetValue(UINT_PTR uiID, int iValue);bool CheckValid(UINT_PTR uiID);void PrepareAllows(UINT_PTR uiID, bool* pbAllowed);//Internal data structure declarations
private:struct stInfo        //Structure nested within the CGrid class - contains all 81 cells in the grid{stInfo() : m_uiID(0), m_iValue(0) {};    //Initialise variablesUINT_PTR m_uiID;int m_iValue;};CArray<stInfo*, stInfo*>m_arCells;class CBlock        //class nested within the CGrid class{public:CBlock(void) {};~CBlock(void) {};bool AddCell(stInfo* pCell);bool Contains(UINT_PTR uiID, int& iValue);bool CheckValue(int iValue);bool PrepareAllowedValues(UINT_PTR uiID, bool* pbAllowed);UINT_PTR GetID(int iIndex) { return m_arCells[iIndex]->m_uiID; } ;int GetValue(int iIndex) { return m_arCells[iIndex]->m_iValue; } ;private:CArray<stInfo*, stInfo*>m_arCells;        //POINTERS to the cells from the CGrid class};//Actual data structuresCBlock m_Row[9];CBlock m_Col[9];CBlock m_Square[9];bool CheckBlock(CBlock* pBlock, UINT_PTR uiID);void ProcessAllowsBlock(CBlock* pBlock, UINT_PTR uiID, bool* pbAllowed);
};

And for the .cpp file:

对于.cpp文件:

CGrid::~CGrid(void)
{//Release any memory assigned with new - prevent memory leaksfor(int i = 0; i < m_arCells.GetCount(); i++)delete m_arCells[i];m_arCells.RemoveAll();
}bool CGrid::AddCell(int iRow, int iCol, UINT_PTR uiID)
{stInfo* pCell = new stInfo;//Sanity - check the ID for uniquenessfor(int i = 0; i < m_arCells.GetCount(); i++){ASSERT(m_arCells[i]->m_uiID != uiID);}pCell->m_uiID = uiID;    //set the identifierm_arCells.Add(pCell);ASSERT(m_arCells.GetCount() <= 81);        //Max 81 cells in the array//Assign cells into blocksif(!m_Row[iRow].AddCell(pCell))return false;if(!m_Col[iCol].AddCell(pCell))return false;int iSquare = (3 * (iRow / 3)) + (iCol / 3);    //Work out which square this row/col combination belongs toif(!m_Square[iSquare].AddCell(pCell))return false;return true;
}void CGrid::SetValue(UINT_PTR uiID, int iValue)
{//Loop through all the cells, et the value of the cell with that IDfor(int i = 0; i < m_arCells.GetCount(); i++){if(m_arCells[i]->m_uiID == uiID){m_arCells[i]->m_iValue = iValue;return;}}ASSERT(FALSE);    //Cell not found for some reason
}bool CGrid::CheckValid(UINT_PTR uiID)
{//Perform a validity check, each digit should only appear once in a block//This needs to be done for all the blocks//Note each block is an array so the variable itself is a POINTER to the first member of the arrayif(!CheckBlock(m_Row, uiID))    return false;if(!CheckBlock(m_Col, uiID))    return false;if(!CheckBlock(m_Square, uiID))    return false;return true;
}bool CGrid::CheckBlock(CBlock* pBlock, UINT_PTR uiID)
{//There are nine individual blocks inside each array of blocks, check each if the cell with this ID is part of it int iValue;for(int i = 0; i < 9; i++){if(pBlock[i].Contains(uiID, iValue))    //is it in this block?  If yes then what value does it contain{return pBlock[i].CheckValue(iValue);}}return true;
}void CGrid::PrepareAllows(UINT_PTR uiID, bool* pbAllowed)
{ProcessAllowsBlock(m_Row, uiID, pbAllowed);ProcessAllowsBlock(m_Col, uiID, pbAllowed);ProcessAllowsBlock(m_Square, uiID, pbAllowed);
}void CGrid::ProcessAllowsBlock(CBlock* pBlock, UINT_PTR uiID, bool* pbAllowed)
{//Each array of blocks has nine entriesfor(int i = 0; i < 9; i++){//delegate to the block to check//A cell can only belong to one row, one col and one square - a return of true meant //the uiID belonged to a cell in this blockif(pBlock[i].PrepareAllowedValues(uiID, pbAllowed))return;}
}//Internal CBlock classbool CGrid::CBlock::AddCell(stInfo* pCell)
{//Only can have 9 cells at mostif(m_arCells.GetCount() > 9){ASSERT(FALSE);return false;}m_arCells.Add(pCell);return true;
}bool CGrid::CBlock::Contains(UINT_PTR uiID, int& iValue)
{iValue = 0;for(int i = 0; i < m_arCells.GetCount(); i++){if(GetID(i) == uiID){iValue = GetValue(i);return true;}}return false;
}bool CGrid::CBlock::CheckValue(int iValue)
{//Value should appear at most once in the blockint iCount = 0;    //initially no instances of this value found//Loop through all cells, increment the number of times we find the value being usedfor(int i = 0; i < m_arCells.GetCount(); i++){if(GetValue(i) == iValue)iCount++;}//used once or less and the value is OK for this blockreturn (iCount <= 1);
}bool CGrid::CBlock::PrepareAllowedValues(UINT_PTR uiID, bool* pbAllowed)
{//returns true if the uiID matches with a member cell of this block//Check if the cell is in fact in this block, process if it isint iValue;  if(Contains(uiID, iValue)){//See which digits are used elsewhere in the block//the lParam passed in the message is the adress of the bool array //cast it to a bool pointer fo accessing it//remember the array is zero based - so if digit 2 is in use we want the array member 1for(int i = 0; i < 9; i++){int iValue = GetValue(i);//If the value is non zero (used) then set the allowed flag to false//A cell can be in other blocks, we can only turn it off - do NOT set to trueif(iValue > 0)pbAllowed[iValue - 1] = false;}return true;}//cell not found in blockreturn false;
}

Most of the code above is fairly straightforward.  A point of interest is the CheckValid and CheckBlock functions of the grid class.  Particularly the passing of arrays via pointers.  You can write some pretty neat (or dangerous or even just extremely difficult to read/understand/maintain) code using pointers.

上面的大多数代码相当简单。 有趣的是网格类的CheckValid和CheckBlock函数。 特别是通过指针传递数组。 您可以使用指针编写一些漂亮的(或危险的,甚至是极其难以阅读/理解/维护的)代码。

Now let’s try it, compile and run it with the F5 key.  In the attached files is a presaved game called partial.  Load this – there is only one cell left to enter a value.  Enter the required digit (hint: 4) and see everything stay blue to indicate a good result.  Hmmm, they don’t do they, things have gone red.  Where is the bug?

现在让我们尝试一下,使用F5键编译并运行它。 附件中有一个保存完好的游戏,称为“部分游戏”。 加载它–仅剩一个单元格可以输入值。 输入所需的数字(提示:4),然后查看所有内容保持蓝色,表明效果良好。 嗯,他们不这样做,事情变红了。 错误在哪里?

查找错误。 (Finding the bug.)

In the CheckValue function of the CBlock we add an extra line then use a breakpoint (F9 key) so we have the following:

在CBlock的CheckValue函数中,我们添加了一条额外的行,然后使用断点(F9键),因此我们具有以下内容:

Run the application, load up this invalid game, enter 1 in the cell that is empty and the debugger should stop at the line with a red dot at the left.  Press F5 again – this runs the program until the line of code is run again.

运行该应用程序,加载此无效的游戏,在空白单元格中输入1,调试器应停在左侧带红点的行。 再次按F5键-这将运行程序,直到再次运行代码行。

If you have a look at one of the debugger information windows – in this case the ‘auto’ window – you should see the current values held within pInfo.  (I have added the line concerning pInfo because the debugger doesn’t cope with the MFC collection classes very well – this simplifies our checking of the contents of the variables.)  The m_uiID is the ID of the cell and the m_iValue is the copy of the value in the cell.  m_iValue=0  Wrong.  It should be non zero but why is it zero.  We have loaded up a saved game, this sets the value in the GridButton – aaah, the view only receives the custom message to update the CBlock from a keyboard entry of the value.

如果您查看其中一个调试器信息窗口(在本例中为“自动”窗口),则应该看到pInfo中保存的当前值。 (我添加了与pInfo有关的行,因为调试器不能很好地处理MFC集合类–这简化了我们对变量内容的检查。)m_uiID是单元格的ID,m_iValue是单元格中的值。 m_iValue = 0错误。 它应该不为零,但为什么为零。 我们已经加载了一个已保存的游戏,这会在GridButton中设置该值-aaah,该视图仅接收自定义消息以从该值的键盘输入中更新CBlock。

Change the following:

更改以下内容:

void CGridButton::SetValue(int i)
{ m_iValue = i; if(GetValue() > 0){CString s((char)(GetValue() + '0')); SetWindowText(s); }elseSetWindowText("");
}

To this:

对此:

void CGridButton::SetValue(int i)
{ m_iValue = i; if(GetValue() > 0){CString s((char)(GetValue() + '0')); SetWindowText(s); }elseSetWindowText(""); GetParent()->SendMessage(SUD_SETVALUE, (WPARAM)this, (LPARAM)GetValue());
}

Now try compiling and testing the invalid solution – bug squashed!

现在尝试编译和测试无效的解决方案-减少了错误!

An aside:

撇开:

There is a lot of functionality built into the Visual Studio development environment to help you track down and correct any bugs in your code.  Often a simple thing like a breakpoint and looking at the value in a variable can be enough to sort out the problem.  The breakpoint key (F9) puts a breakpoint onto a line of code, the debugger halts execution when that line of code is reached.  Now look at the debug menu – things like step into, step over and step out can help you move through the execution of your code, there are windows you can use to monitor the values of variables.  More advanced stuff includes the possibility to put optional arguments onto a breakpoint – instead of stopping everytime and checking if a variable has a value under 17 only stop when it is NOT under 17 for instance.

Visual Studio开发环境中内置了许多功能,可以帮助您跟踪和纠正代码中的所有错误。 通常,简单的事情(例如断点)和查看变量中的值就足以解决问题。 断点键(F9)将断点放在代码行上,调试器在到达该行代码时暂停执行。 现在看一下调试菜单-进入,跳出和跳出之类的内容可以帮助您逐步执行代码,有些窗口可用于监视变量的值。 更高级的内容包括可以将可选参数置于断点上的可能性,而不是每次都停止并检查变量值是否小于17的情况,只有在不小于17时才停止。

Now we can concentrate on the hints.  You might have noticed that in the DrawItem routine of the CGridButton there was an else statement that would be run should the button not have a value.  Here we will provide a visual feedback for the user about what numbers could be the required one.

现在我们可以专注于提示。 您可能已经注意到,在CGridButton的DrawItem例程中,如果按钮没有值,则将运行else语句。 在这里,我们将为用户提供有关可能需要多少个数字的视觉反馈。

This is the code we require (GridButton.cpp file, DrawItem function):

这是我们需要的代码(GridButton.cpp文件,DrawItem函数):

    else{//Draw hintCString s;CRect rc;GetClientRect(&rc);int iWidth = rc.Width() / 3;int iHeight = rc.Height() / 3;dc.SetTextColor(RGB(128,128,0));bool bAllow[9];//Set the flags all to be truememset(bAllow, true, sizeof(bAllow));GetParent()->SendMessage(SUD_PREPAREALLOWS, (WPARAM)this, (LPARAM)&bAllow);CRect rcClient;for(int row = 0; row < 3; row++){for(int col = 0; col < 3; col++){if(bAllow[row*3 + col]){rcClient.top = row * iHeight;    rcClient.bottom = rcClient.top + iHeight;rcClient.left = col * iWidth;    rcClient.right = rcClient.left + iWidth;s.Format(_T("%d"), row*3 + col + 1);    //Zero based array but contents start at onedc.DrawText(s, s.GetLength(), &rcClient, DT_SINGLELINE|DT_VCENTER|DT_CENTER);}}}}

Note we are using a local variable bAllow – the array of bool’s.  The address of this is being passed through the SendMessage function after being cast to an LPARAM.

请注意,我们使用的是局部变量bAllow-布尔数组。 此地址在转换为LPARAM后将通过SendMessage函数传递。

SendMessage vs PostMessage.SendMessage与PostMessage。

I’ve not said anything about this so far but there are two windows API functions for passing messages between windows.  SendMessage and PostMessage.  Both take the same parameters.  The important difference is that SendMessage waits for the recipient to process it (or windows to throw it away if no window has a handler for it) wheras PostMessage just adds the message to the recipient windows message queue then returns.  There are two very different things to bear in mind here.  SendMessage can lead to a deadlock (eg. A sends to B and as part of the processing B send to A).  PostMessage continues so you don’t know when the recipient will actually use the message, passing an address of a local variable (as here) will almost certainly lead to errors if not crashes in your app.

到目前为止,我还没有说什么,但是有两个Windows API函数可以在Windows之间传递消息。 SendMessage和PostMessage。 两者都采用相同的参数。 重要的区别在于,SendMessage等待收件人处理它(如果没有窗口没有处理程序,则窗口将其丢弃),而PostMessage只是将消息添加到收件人Windows消息队列中然后返回。 这里有两件事要牢记。 SendMessage可能导致死锁(例如,A发送到B,并且作为处理B的一部分发送到A)。 PostMessage继续进行,因此您不知道收件人何时实际使用该消息,传递局部变量的地址(如此处所示)几乎可以肯定会导致错误,如果您的应用程序没有崩溃。

We also need to define the message we are passing – in the header of the GridButton class, next to the currently existing custom message

我们还需要定义要传递的消息-在GridButton类的标头中,当前存在的自定义消息旁边

#define SUD_SETVALUE (WM_USER+101)
#define SUD_PREPAREALLOWS (WM_USER+102)

In the SudokuView we now should add the code for the handlers of this message.  In the header file we need a declaration for that function and another function it will use:

现在,在SudokuView中,我们应该为该消息的处理程序添加代码。 在头文件中,我们需要该函数的声明以及它将使用的另一个函数:

    afx_msg LRESULT OnPrepareAllows(WPARAM wParam, LPARAM lParam);

In the SudokuView.cpp file add the following line to the BEGIN_MESSAGE_MAP

在SudokuView.cpp文件中,将以下行添加到BEGIN_MESSAGE_MAP

    ON_MESSAGE(SUD_PREPAREALLOWS, &CSudokuView::OnPrepareAllows)

Then to the SudokuView.cpp file we add

然后添加到SudokuView.cpp文件中

LRESULT CSudokuView::OnPrepareAllows(WPARAM wParam, LPARAM lParam)
{m_Grid.PrepareAllows((UINT_PTR)wParam, (bool*)lParam);return 1;
}

Now we need to add some more code to the CGrid class for supporting the preparation of what is an allowed value.  In Grid.h we need a public function:

现在,我们需要向CGrid类添加更多代码,以支持准备允许的值。 在Grid.h中,我们需要一个公共函数:

    void PrepareAllows(UINT_PTR uiID, bool* pbAllowed);

and a private function

和一个私人功能

    void ProcessAllowsBlock(CBlock* pBlock, UINT_PTR uiID, bool* pbAllowed);

in the .cpp file

在.cpp文件中

void CGrid::PrepareAllows(UINT_PTR uiID, bool* pbAllowed)
{ProcessAllowsBlock(m_Row, uiID, pbAllowed);ProcessAllowsBlock(m_Col, uiID, pbAllowed);ProcessAllowsBlock(m_Square, uiID, pbAllowed);
}void CGrid::ProcessAllowsBlock(CBlock* pBlock, UINT_PTR uiID, bool* pbAllowed)
{//Each array of blocks has nine entriesfor(int i = 0; i < 9; i++){//delegate to the block to check//A cell can only belong to one row, one col and one square - a return of true meant //the uiID belonged to a cell in this blockif(pBlock[i].PrepareAllowedValues(uiID, pbAllowed))return;}
}

Now in the internal CBlock class we declare a public function:

现在,在内部CBlock类中,我们声明一个公共函数:

        bool PrepareAllowedValues(UINT_PTR uiID, bool* pbAllowed);

And implement it thus:

并这样实现:

bool CGrid::CBlock::PrepareAllowedValues(UINT_PTR uiID, bool* pbAllowed)
{//returns true if the uiID matches with a member cell of this block//Check if the cell is in fact in this block, process if it isint iValue;  if(Contains(uiID, iValue)){//See which digits are used elsewhere in the block//the lParam passed in the message is the adress of the bool array //cast it to a bool pointer for accessing it//remember the array is zero based - so if digit 2 is in use we want the array member 1for(int i = 0; i < 9; i++){int iValue = GetValue(i);//If the value is non zero (used) then set the allowed flag to false//A cell can be in other blocks, we can only turn it off - do NOT set to trueif(iValue > 0)pbAllowed[iValue - 1] = false;}return true;}//cell not found in blockreturn false;
}

Now compile and run the application.  You should see every cell displaying a small grid of 1..9.  Now load the test game and you should see something like:

现在编译并运行该应用程序。 您应该看到每个单元格都显示一个小网格1..9。 现在加载测试游戏,您应该看到类似以下内容:

This is now displaying a hint to the user as to what is available, type in a five into the cell in the second row, first column – you should see the number five disappear as a hint from row 2, column 1 and the top left square of the grid.

现在这将向用户显示有关可用内容的提示,在第二行第一列的单元格中键入5,您应该看到数字5从第2行,第1列和左上角消失了网格的正方形。

Does this make it too simple?  Lets just show the hints for the button the mouse is over.  For this we need to look at mouse move events.

这是否太简单了? 让我们只显示鼠标悬停按钮的提示。 为此,我们需要查看鼠标移动事件。

Make a small change to the CSudokuView::PreTranslateMessage function

对CSudokuView :: PreTranslateM进行一些小的更改 留言功能

    if(pMsg->message == WM_KEYDOWN)

becomes

变成

    if(pMsg->message == WM_MOUSEMOVE)ProcessMouseMove(pMsg->pt);    //screen co-ordinateselse if(pMsg->message == WM_KEYDOWN)

Now add a new private function - .h file

现在添加一个新的私有函数-.h文件

    void ProcessMouseMove(CPoint pt);

and .cpp file

和.cpp文件

void CSudokuView::ProcessMouseMove(CPoint pt)
{ScreenToClient(&pt);static CWnd* spLastWindow = NULL;    //reduce flickering when mouse moving on a buttonCWnd* pChild = ChildWindowFromPoint(pt);if((pChild != spLastWindow) && (pChild != this)){if(spLastWindow != NULL){//Unset - mouse moving away from this windowstatic_cast<CGridButton*>(spLastWindow)->ShowHints(false);spLastWindow->Invalidate();spLastWindow = NULL;}if(pChild != NULL){//Find which button, if any, it isfor(int i = 0; i < 81; i++){if(&m_arWndButtons[i] == pChild){m_arWndButtons[i].ShowHints(true);m_arWndButtons[i].Invalidate();spLastWindow = &m_arWndButtons[i];}}}}
}

And in the CGridButton we need a couple of lines in the .h file to support this temporary display of the hints:

在CGridButton中,我们需要在.h文件中添加几行来支持提示的这种临时显示:

public:void ShowHints(bool bFlag) {m_bShowHint = bFlag; };
private:bool m_bShowHint;

and in the DrawItem we need a minor change to the hint drawing routine, the following is changed from:

在DrawItem中,我们需要对提示绘制例程进行一些小的更改,以下内容从以下更改:

    else{//Draw hint

to:

至:

    else if(m_bShowHint){//Draw hint

The m_bShowHint is set to false in the constructor’s initialisation list

在构造函数的初始化列表中,将m_bShowHint设置为false

,    m_bValid(true)
,    m_bShowHint(false)

Rebuild and run (F5 key), load the saved game and move the mouse around.  The hints should only appear when the mouse is over a button on the game.

重建并运行(按F5键),加载保存的游戏并四处移动鼠标。 仅当鼠标悬停在游戏上的按钮上时,提示才会出现。

Design – having a copy of the value in the gridbutton (m_iValue) is a really poor design, it should have a pointer to the structure containing the data for instance, at least something that doesn't duplicate data.  It would even simplify matters when entering a value from the keyboard.  However it meant I couldn’t demonstrate some techniques as I did do.

设计 –在gridbutton(m_iValue)中具有值的副本是一个非常糟糕的设计 ,例如,它应该具有一个指向包含数据的结构的指针,至少不重复数据。 通过键盘输入值时,甚至可以简化事情。 但是,这意味着我无法像我一样演示一些技术。

结论: (Conclusion:)

We have extended our ownerdrawing to provide 'on the fly' visual hints.

我们扩展了ownerdrawing,以提供“即时”视觉提示。

We have nested classes to hide internal structures and used a templated collection for in memory data storage with the extra security the template brings.

我们有嵌套的类来隐藏内部结构,并使用模板化的集合用于内存数据存储,并具有模板带来的额外安全性。

We have had an introduction to debugging with breakpoints and watch windows.

我们已经介绍了使用断点和监视窗口进行调试的方法。

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

Previous article in the series is here:  Sudoku in MFC: Part 6

该系列中的上一篇文章在这里: MFC中的数独:第6部分

There we implemented a sinlgeton class and coded owner drawing of the button.

在那里,我们实现了一个sinlgeton类,并对按钮的所有者进行了编码。

Next article in the series is here:  Sudoku in MFC: Part 8

该系列的下一篇文章在这里: MFC中的Sudoku:第8部分

Here we will be looking at working with a database to store and retrieve data in tables.  We will also find how to locate the exe file on the disc, generating random numbers for playing games randomly selected from storage and some simple error trapping.

在这里,我们将研究使用数据库来存储和检索表中的数据。 我们还将找到如何在光盘上找到exe文件,如何生成随机数来玩从存储中随机选择的游戏以及一些简单的错误陷阱。

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.

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

翻译自: https://www.experts-exchange.com/articles/3827/Sudoku-a-complete-MFC-application-Part-7.html

sudoku me

sudoku me_Sudoku,一个完整的MFC应用程序。 第7部分相关推荐

  1. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(三) --高级设置一...

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(三) --高级设置一 原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(三) --高级设置一 上 ...

  2. Flutter 构建一个完整的聊天应用程序

    在本教程中,我将向您展示如何使用 Flutter 构建一个完整的聊天应用程序.对于这一部分,我们将创建应用程序的 UI 原型,然后我将向您展示如何使用 firebase 创建后端服务并创建聊天系统. ...

  3. java 程序输出 赵_编写一个完整的JAVA的程序

    编写一个完整的JAVA的程序 关注:84  答案:1  mip版 解决时间 2021-02-05 08:43 提问者妳螚鬧俄螚笑 2021-02-05 02:59 1,接口Person Show()方 ...

  4. 一个完整的Installshield安装程序实例

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(一)---基本设置一 前言 Installshield可以说是最好的做安装程序的商业软件之一,不过因为功能的太过于强大,以至于 ...

  5. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(三) --高级设置一

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(三) --高级设置一 上一篇:一个完整的安装程序实例-艾泽拉斯之海洋女神出品(二) --基本设置二 第二部分:脚本编程 在开始 ...

  6. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(四) --高级设置二...

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(四) --高级设置二 原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(四) --高级设置二 上 ...

  7. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(五) --补遗 (已补充第三部分完整版)...

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(五) --补遗 (已补充第三部分完整版) 原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(五) ...

  8. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(二) --基本设置二...

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(二) --基本设置二 原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(二) --基本设置二 上 ...

  9. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(一)---基本设置一...

    一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(一)---基本设置一 原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(一)---基本设置一 前 ...

最新文章

  1. Wordpress 加入html等文件
  2. 如何将一个列表当作元组的一个元素
  3. mysql grant 不想让用户看到 系统默认 mysql_MYSQL用户权限管理GRANT使用
  4. web ch6 表单基础(部分选学)
  5. Microsoft Azure 中的 SharePoint Server 2013 灾难恢复
  6. 美女学霸直博中科院,本科武大王者全国16强,妥妥现实版“爽文女主”!
  7. 笔记:Microservices for Java Developers
  8. HubbleDotNet 基本语法
  9. 基于 EntityFramework、Autofac 的 UnitOfWork 框架(一)
  10. 【转】WF4.0实战系列索引
  11. BC26 OpenCPU System API接口
  12. 测试过程之UT-IT-ST的区别
  13. 寂寞的季节C调吉他谱 - 陶喆
  14. springBoot接入阿里云oss
  15. thinkphp的column()函数
  16. mysql .frm文件丢失_实例中所有frm文件消失的幕后黑手
  17. SE5_FALSR超分辨率图像模型移植与测试
  18. 我爱赚钱吧:你知道自己建网站可以赚钱吗?①
  19. 手把手教你用JSP完成登录注册插入数据库数据
  20. Py西游攻关之迭代器生成器

热门文章

  1. KVM - qcow2 和 raw 格式对比
  2. 云聚创新力量,助力多云互联:Tungsten Fabric在联通沃云峰会2019上分享开源SDN
  3. 蚂蚁电竞ANT27VQ电子竞技显示器重磅来袭
  4. 使用Jekyll + GitHub 搭建自己的博客
  5. upload-labs刷关记录
  6. 时间转换 秒(s)转 ()天 ()小时() 分钟 ()秒
  7. vite + react + ts 配置路径别名alias
  8. 程序员日常,你的痛只有我懂,因为小编也是程序员,扎心不老铁?
  9. Linux 修改系统时间为东八区时间
  10. 博客 Gif 动态图制作 - 插入gif动态图 GifCam