sudoku me_Sudoku,一个完整的MFC应用程序。 第三部分

sudoku me

介绍: (Introduction:)

在SDI内加载和保存Document-View交互文件。

Continuing from the second article about sudoku.  

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

Open the project in visual studio.

在Visual Studio中打开项目。

From the class view select CSudokuDoc and double click to open the header file.  At the end of the file (before the closing brace) add the following lines:

从类视图中,选择CSudokuDoc并双击以打开头文件。 在文件末尾(大括号之前)添加以下行:

private:
    char m_arGame[82];
public:
    enum {eLoadGame, eSaveGame};
    char* GetGame() { return m_arGame; };

We are going to store the contents of the grid (81 squares) in the character array.  I have added an extra character to store a terminating null for string manipulation options.  The enum defines two values (eLoadGame and eSaveGame) for usage in the app.  One could just hard code with eg. 1 and 2 but which is more understandable?  Using eLoadGame helps one understand what is going on.

我们将把网格的内容(81个正方形)存储在字符数组中。 我添加了一个额外的字符来存储字符串操作选项的终止null。 枚举定义两个值(eLoadGame和eSaveGame)供应用程序使用。 一个人可以用例如硬编码。 1和2,但哪个更容易理解? 使用eLoadGame可以帮助您了解发生了什么。

From the class view navigate to the OnNewDocument function of CSudokuDoc, it should be as follows:

从类视图导航到CSudokuDoc的OnNewDocument函数,它应如下所示:

BOOL CSudokuDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

    // TODO: add reinitialization code here
    // (SDI documents will reuse this document)

    return TRUE;
}

Change the code to the following:

将代码更改为以下内容:

    if (!CDocument::OnNewDocument())
        return FALSE;

    strcpy_s(m_arGame, sizeof(m_arGame), "000000000000000000000000000000000000000000000000000000000000000000000000000000000");
    strcpy_s(m_arGame, sizeof(m_arGame), "073200000090600042001000708000000065000050000810000000104000500260003090000004210");
    UpdateAllViews(NULL, eLoadGame, NULL);

    return TRUE;

The first call to strcpy_s will fill the character array with the character 0 – which is NOT a zero.  The second call is just temporary, it is a basis game of sudoku – we will remove it later.  Then we instruct the view that something in the document has changed, note we pass the eLoadGame as a hint to the view receiving the update information.

首次调用strcpy_s将用字符0填充字符数组-不为零。 第二次通话只是暂时的,它是数独的基础游戏–我们稍后将其删除。 然后,我们指示视图文档中的某些内容已更改,请注意,我们将eLoadGame作为提示传递给接收更新信息的视图。

Now from the class view with add a new override function – OnUpdate and modify it to be the following:

现在,从类视图中添加一个新的覆盖功能– OnUpdate并将其修改为以下内容:

void CSudokuView::OnUpdate(CView* pSender, LPARAM lHint, CObject* /*pHint*/)
{
    if(!m_bInitialised)
        return;

    char* pch = GetDocument()->GetGame();
    switch(lHint)
    {
    case CSudokuDoc::eLoadGame:
        //Fill the grid contents
        for(int row = 0; row < 9; row++)
        {
            for(int col = 0; col < 9; col++)
            {
                m_arWndButtons[row * 9 + col].SetValue(*pch - '0');
                m_arWndButtons[row * 9 + col].SetLock();
                pch++;
            }
        }
        Invalidate();
        break;
    case CSudokuDoc::eSaveGame:
        //Update the doc 'game' string with the current grid contents
        for(int row = 0; row < 9; row++)
        {
            for(int col = 0; col < 9; col++)
            {
                *pch = m_arWndButtons[row * 9 + col].GetValue() + '0';
                pch++;
            }
        }
        break;
    default:
        ASSERT(FALSE);
        break;
    }
}

We need to stop the code being executed until after the buttons are subclassed – hence the if statement at the start.  Part of the code will change the text of the buttons, but that will crash if the button does not have a window.  The subclassing will assign a window to a button in our array.  (You could try commenting that check out to see what happens if you like).  In a later article the requirement for this check will be removed.  Note in the case statement we use the hint passed and perform one piece of code depending on the value of the hint.  Notice how much more readable case CSudokuDoc::eLoadGame: is than case 1:

我们需要停止执行代码,直到对按钮进行了子类化为止-因此if语句从头开始。 部分代码将更改按钮的文本,但是如果按钮没有窗口,则将崩溃。 子类将为我们的数组中的按钮分配一个窗口。 (您可以尝试注释该签出,以查看会发生什么情况)。 在以后的文章中,将删除此检查的要求。 注意,在case语句中,我们使用传递的提示,并根据提示的值执行一段代码。 请注意,情况CSudokuDoc :: eLoadGame:比情况1更易读:

Also see how we can move through the character array.  We have a pointer to the array (pch) and can move from one member to the next just by incrementing the array pointer.  

另请参阅我们如何遍历字符数组。 我们有一个指向数组(pch)的指针,只需增加数组指针就可以从一个成员移动到下一个成员。

Note also that we change a character 0 into an integer 0 with *pch – ‘0’  which means take the value of the character 0 from the value stored at memory location pch.

还要注意,我们用* pch –'0'将字符0更改为整数0,这意味着从存储在存储器位置pch中的值中提取字符0的值。

Retrieving values behaves in a similar manner.  (Note that misusing the buffer pointer can allow one to write into memory that is not part of the array – typically leading to a crash.)

检索值的行为类似。 (请注意,滥用缓冲区指针可能会使人将其写入不属于数组的内存中,这通常会导致崩溃。)

This code won’t compile because SetValue, SetLock and GetValue are not functions in the MFC CButton class.  (Try to compile if you like so you see the error messages.  The error C2039 is often seen because one mixes the case up when typing the function eg. SetWindowtext instead of SetWindowText)

由于SetValue,SetLock和GetValue不是MFC CButton类中的函数,因此无法编译此代码。 (如果愿意,请尝试编译,以查看错误消息。通常会出现

Now we need a class derived from CButton and we will be using this new class in the view.  Open the solution view and right click on Sudoku to see the context menu.  Choose Add then Class, a wizard titled Add Class – Sudoku should appear.  Choose MFC from the tree view at the left, then select MFC Class from the Visual Studio Installed templates.  Click the Add button – you should now have the MFC Class wizard available.  Enter a name for the new class – CGridButton, from the Base class combo select CButton, now click finish.  A file GridButton.h should be open for editing.  You should see we have a constructor, a virtual destructor and a couple of macros (DECLARE_DYNAMIC and DECLARE_MESSAGE_MAP) provided.

现在,我们需要一个派生自CButton的类,并将在视图中使用此新类。 打开解决方案视图,然后右键单击Sudoku以查看上下文菜单。 选择添加,然后选择类,将出现一个标题为添加类– Sudoku的向导。 从左侧的树视图中选择MFC,然后从Visual Studio已安装模板中选择MFC类。 单击“添加”按钮–现在,您应该可以使用MFC类向导。 输入新类的名称-CGridButton,从基类组合中选择CButton,然后单击完成。 应该打开文件GridButton.h进行编辑。 您应该看到我们有一个构造函数,一个虚拟析构函数和几个宏(DECLARE_DYNAMIC和DECLARE_MESSAGE_MAP)。

Now open SudokuView.h and near the bottom we require a change from

现在打开SudokuView.h,在底部附近,我们需要从

CButton m_arWndButtons[81];

To

CGridButton m_arWndButtons[81];

If we tried to compile (F7 key) there should be an error C2146 about syntax error.  This tells us that the CGridButton is unknown to the compiler at this point in this file.  Go to the top of SudokuView.h and add an extra line so we have:

如果我们尝试编译(F7键),则应该

#pragma once
#include "GridButton.h"

This indicates to the compiler that it should also look into this file (GridButton.h) for declarations/definitions.  

这向编译器指示它也应该在该文件(GridButton.h)中进行声明/定义。

Now from the class view open the header file for CGridButton – you should see this for the class:

现在从类视图中打开CGridButton的头文件–您应该在类中看到以下文件:

class CGridButton : public CButton
{
    DECLARE_DYNAMIC(CGridButton)

public:
    CGridButton();
    virtual ~CGridButton();

protected:
    DECLARE_MESSAGE_MAP()
};

We need to add some code so it looks like this:

我们需要添加一些代码,如下所示:

class CGridButton : public CButton
{
    DECLARE_DYNAMIC(CGridButton)

public:
    CGridButton();
    virtual ~CGridButton();

protected:
    DECLARE_MESSAGE_MAP()

private:
    bool m_bLock;
    int m_iValue;    //temporary

public:
    void SetValue(int i);
    int GetValue() { return m_iValue; };

    void SetLock() { m_bLock = (GetValue() != 0); };    //Lock only if no value entered
};

And in the GridButton.cpp file at the end add the following:

并在GridButton.cpp文件的末尾添加以下内容:

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

Also we ought to initialise the variables we have declared:

我们还应该初始化我们声明的变量:

CGridButton::CGridButton()
:    m_iValue(0)
,    m_bLock(false)

Now it should compile and we can run the application.  When it starts the buttons all display Button1 as the text, press the ‘New’ button or select it from the menu or with Ctrl+N then the display changes to numbers.  Progress is being made.  

现在应该可以编译了,我们可以运行应用程序了。 当它启动所有按钮时,所有按钮都将Button1显示为文本,按“新建”按钮,或者从菜单中选择它,或者使用Ctrl + N进行选择,然后显示变为数字。 正在取得进展。

We now have a game – so let’s write the code to save this game then tidy up our OnNewDocument to what it should be.

现在我们有了一个游戏–因此,让我们编写代码来保存该游戏,然后将OnNewDocument整理成应有的样子。

Go to the Serialize function in CSudokuDoc, it should be this:

转到CSudokuDoc中的Serialize函数,应该是这样的:

void CSudokuDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        // TODO: add storing code here
    }
    else
    {
        // TODO: add loading code here
    }
}

Now change it to this:

现在将其更改为:

void CSudokuDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        UpdateAllViews(NULL, eSaveGame, NULL);
        COleDateTime dte = COleDateTime::GetCurrentTime();
        m_szInfo = dte.Format("%d.%m.%y  %H:%M");
        CString szGame(m_arGame);
        ar << m_szInfo << szGame;
    }
    else
    {
        CString szGame;
        ar >> m_szInfo >> szGame;
        strcpy_s(m_arGame, sizeof(m_arGame), szGame);
        UpdateAllViews(NULL, eLoadGame, NULL);
    }
}

Here we are going to store some extra information – a date/time stamp and the game.  Have a look at the else statement.  This reads from the archive, then we use UpdateAllViews to inform the view that we have something to display.  If you compile and run you will see that MFC supplies a lot of functionality for you – it prompts for the file, warns you about overwriting…

在这里,我们将存储一些其他信息-日期/时间戳和游戏。 看一下else语句。 这是从存档中读取的,然后我们使用UpdateAllViews通知视图我们要显示一些内容。 如果编译并运行,您会发现MFC为您提供了许多功能-提示输入文件,警告您有关覆盖…

To make this work we need the one new variable – open the header file for the document and add one protected member variable (eg. next to our char array) like so:

为了完成这项工作,我们需要一个新变量–打开文档的头文件,并添加一个受保护的成员变量(例如,在char数组旁边),如下所示:

private:
    CString m_szInfo;
    char m_arGame[82];

In the OnNewDocument we should also reset the contents of these variables.  Also we can now remove our dummy game because we have one saved.  OnNewDocument should look like this:

在OnNewDocument中,我们还应该重置这些变量的内容。 现在我们也可以删除虚拟游戏,因为我们已经保存了一个。 OnNewDocument应该看起来像这样:

    strcpy_s(m_arGame, sizeof(m_arGame), "000000000000000000000000000000000000000000000000000000000000000000000000000000000");
    //strcpy_s(m_arGame, sizeof(m_arGame), "073200000090600042001000708000000065000050000810000000104000500260003090000004210");
    m_szInfo.Empty();

Build and run the app, everytime we click the new document button the grid is filled with zero’s but we can also load the previously saved game – give it a try.

生成并运行该应用程序,每次我们单击“新文档”按钮时,网格将填充零,但我们也可以加载以前保存的游戏-试试看。

结论: (Conclusion:)

We have loaded and saved data from / to the hard disc.

我们已经将数据从/加载并保存到硬盘。

We have seen how the document / view architecture works.

我们已经看到了文档/视图架构的工作方式。

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

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

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

There we looked at dynamically positioning controls on a window from code.  We also used the windows registry for storing information about the application.

在那里,我们研究了通过代码在窗口上动态定位控件。 我们还使用Windows注册表存储有关应用程序的信息。

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

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

There we will be customising the status bar (at the bottom of the window) to display information about the game in progress to the user.

我们将自定义状态栏(在窗口底部),以向用户显示有关正在进行的游戏的信息。

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/3822/Sudoku-a-complete-MFC-application-Part-3.html

sudoku me

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值