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

sudoku me

介绍: (Introduction:)

撤消支持,实现堆栈。

Continuing from the eigth article about sudoku.  

续上有关数独的第八篇文章。

We need a mechanism to keep track of the digits entered so as to implement an undo mechanism.  This should be a ‘Last In First Out’ collection – basically a stack.  MFC supplies various collections, unfortunately a stack is not one of them.  We require what digit was the previous value and to which button.  Where to implement this?  Typically a document would be for the data storage but we are not implementing Sudoku in that way.  Here the buttons themselves (on the view) store the information – so I will implement this undo feature in the view.  An alternative would have been to store the previous values within each button itself – so all we would require was which button was last changed (but I coded it this way before I had that idea, and this way does work - decisions, choices, for a real application plan, plan PLAN! - there is no substitute for that).

我们需要一种机制来跟踪输入的数字,以实现撤消机制。 这应该是“后进先出”集合-基本上是堆栈。 MFC提供了各种集合,不幸的是堆栈不是其中之一。 我们需要哪个数字是先前的值以及哪个按钮。 在哪里实施? 通常,文档将用于数据存储,但我们并不是以这种方式实现Sudoku。 这里的按钮本身(在视图上)存储信息-因此,我将在视图中实现此撤消功能。 一种替代方法是将先前的值存储在每个按钮本身中-因此,我们需要的是最后一次更改哪个按钮(但是我在有了这个主意之前就以这种方式进行了编码,并且这种方式可以工作-决策,选择,一个真正的申请计划,计划PLAN!-不能替代)。

First we need to create a new class – CUndoStack – for the storage mechanism.  Solution explorer, Add class, C++ class, Add – enter CUndoStack as the name and accept the defaults.  Modify the header file to be as follows:

首先,我们需要为存储机制创建一个新类CUndoStack。 解决方案资源管理器,添加类,C ++类,添加–输入CUndoStack作为名称并接受默认值。 修改头文件,如下所示:

#pragma once

class CUndoStack
{
public:
    CUndoStack(UINT nUndoMessage);
    ~CUndoStack(void);

    void AddItem(CWnd* pWndBtn, int digit);
    void RemoveItem();
    bool HasItems();
    void Reset();

private:
    UINT m_nUndoMessage;

    struct strUndo
    {
        int m_iDigit;
        CWnd* m_pWndBtn;
    };
    CList<strUndo*, strUndo*> m_lstUndo;

    bool m_bInRemove;
};

And the c.pp to be as follows

和c.pp如下

#include "StdAfx.h"
#include "UndoStack.h"

CUndoStack::CUndoStack(UINT nUndoMessage)
: m_nUndoMessage(nUndoMessage), m_bInRemove(false)
{
}

CUndoStack::~CUndoStack(void)
{
    //Clean up the internal list - prevent memory leaks
    Reset();
}

void CUndoStack::AddItem(CWnd* pWndBtn, int digit)
{
    //The removal sends a message to the window - this is to prevent the item being added to the list again
    if(m_bInRemove)
        return;

    strUndo* pUndo = new strUndo;
    pUndo->m_pWndBtn = pWndBtn;
    pUndo->m_iDigit = digit;
    m_lstUndo.AddTail(pUndo);
}

void CUndoStack::RemoveItem()
{
    if(HasItems())    //No items - then nothing to do
    {
        strUndo* pUndo = m_lstUndo.RemoveTail();    //unlink from the internal list

        //Set the internal flag to prevent re-addition to the list of undo items
        m_bInRemove = true;
        pUndo->m_pWndBtn->SendMessage(m_nUndoMessage, (WPARAM)(pUndo->m_iDigit), (LPARAM)0);
        m_bInRemove = false;

        delete pUndo;    //release memory
    }
}

bool CUndoStack::HasItems()
{
    return (m_lstUndo.GetHeadPosition() != NULL);
}

void CUndoStack::Reset()
{
    POSITION pos = m_lstUndo.GetHeadPosition();
    while(pos != NULL)
    {
        delete m_lstUndo.GetNext(pos);    //release memory
    }
    m_lstUndo.RemoveAll();    //Remove from the list - memory no longer valid
}

We have supplied functions to add the latest digit into the collection.  To perform an undo we just remove the top item – the caller does not need to know what it is.  For updating the availability of the undo option on the toolbar we have the HasItems functions and a Reset which is used when a new game is started.  Note the constructor with the nUndoMessage.  To construct an instance of this class an UINT is required.  This is the message that is to be sent to the recipient window for performing the undo action.  Here is a trivial usage of such a technique.  In a real situation it is providing a bit more separation between the class instances.  A generic solution might uses different messages being sent to different types of windows depending upon circumstances.  Passing the message via the constructor means at compile time one could trap the instance of forgetting to set this parameter.  

我们提供了将最新数字添加到集合中的功能。 要执行撤消操作,我们只需要删除最上面的一项即可-呼叫者不需要知道它是什么。 为了更新工具栏上撤消选项的可用性,我们具有HasItems函数和一个Reset,在启动新游戏时会使用该Reset。 注意带有nUndoMessage的构造函数。 要构造此类的实例,需要使用UINT。 这是要发送到收件人窗口以执行撤消操作的消息。 这是这种技术的琐碎用法。 在实际情况下,它在类实例之间提供了更多的分隔。 通用解决方案可能会根据情况使用将不同的消息发送到不同类型的窗口。 通过构造函数传递消息意味着在编译时可能会捕获忘记设置此参数的实例。

A minor point with the RemoveTail (in the RemoveItem function) is that it does NOT release any memory assigned with new, all it does it detach it from the list.  Failure to understand things like that are a common source of memory leaks.

与RemoveTail(在RemoveItem函数中)相比,还有一点要注意的是,它不会释放分配给new的任何内存,而是将其从列表中分离出来。 无法理解此类情况是导致内存泄漏的常见原因。

Internally to this class is the structure to hold the two pieces of information and a templated list to store the undo items.  We also have a boolean flag (m_bInRemove) which is to stop an item being added to the list as part of the undo.  Sounds odd?  The undo is actually implemented by setting the value in the button to the value it was prior to the change – basically just as if the previous value had been typed in.  Obviously if a number is entered then it is to be added to the undo list.  

该类的内部是保存两部分信息的结构和用于存储撤消项的模板化列表。 我们还有一个布尔标志(m_bInRemove),用于停止将某项作为撤消操作的一部分添加到列表中。 听起来很奇怪? 撤消实际上是通过将按钮中的值设置为更改前的值来实现的-基本上就像输入先前的值一样。显然,如果输入了一个数字,则将其添加到撤消列表中。

We send a message to the GridButton to perform the undo action, basically use the supplied digit – so we require a change in the GridButton.h file.  At the top of the file we add two more definitions:

我们向GridButton发送一条消息以执行撤消操作,基本上使用提供的数字-因此我们需要在GridButton.h文件中进行更改。 在文件的顶部,我们添加了两个定义:

#define SUD_PRESETVALUE (WM_USER+103)
#define SUD_UNDO (WM_USER+104)

Then add the following as a private member at the end of the class declaration:

然后在类声明的末尾添加以下内容作为私有成员:

    afx_msg LRESULT OnUndo(WPARAM wParam, LPARAM lParam);

and in the GridButton.cpp file add a message map entry and the function definition:

然后在GridButton.cpp文件中添加一个消息映射条目和函数定义:

BEGIN_MESSAGE_MAP(CGridButton, CButton)
    ON_MESSAGE(SUD_UNDO, &CGridButton::OnUndo)
END_MESSAGE_MAP()
and

LRESULT CGridButton::OnUndo(WPARAM wParam, LPARAM lParam)
{
    SetValue((int)wParam);
    return 0;
}

This usage of the currently existing function SetValue will ensure that things are performed as if the value was typed in.  The SUD_UNDO is the custom message being used, but what is with the SUD_PRESETVALUE ?  Well to implement the undo functionality we need to ‘know’ what the previous value was.  So when a button has the value changed by keyboard entry we want the view to update the undo list.  The current implementation however sets the value then informs the view, so the simplest alternative is to instruct the parent prior to updating the internal value.  In other word inside the Pretranslate Message function we change

当前使用的函数SetValue的这种用法将确保执行操作就像键入值一样。SUD_UNDO是正在使用的自定义消息,但是SUD_PRESETVALUE是什么? 为了实现撤消功能,我们需要“知道”先前的值。 因此,当按钮的值通过键盘输入更改时,我们希望视图更新撤消列表。 但是,当前实现会先设置值,然后通知视图,因此最简单的选择是在更新内部值之前指示父级。 换句话说,在Pretranslate Message函数中,我们进行了更改

        if(iKey >= 0)
        {
            SetValue(iKey);
            return TRUE;
        }

into:

变成:

        if(iKey >= 0)
        {
            GetParent()->SendMessage(SUD_PRESETVALUE, (WPARAM)this, (LPARAM)GetValue());
            SetValue(iKey);
            GetParent()->SendMessage(SUD_SETVALUE, (WPARAM)this, (LPARAM)iKey);
            return TRUE;
        }

So the parent receives the previous value of the button, then the value is changed and the interface updated.

因此,父级将收到按钮的先前值,然后更改该值并更新接口。

Onto the view.  Put the following into the SudokuView.h file as private members of the class declaration:

进入视图。 将以下内容作为类声明的私有成员放入SudokuView.h文件中:

    CUndoStack m_stackUndo;
    afx_msg LRESULT OnPreSetValue(WPARAM wParam, LPARAM lParam);
    afx_msg void OnEditUndo();
    afx_msg void OnUpdateEditUndo(CCmdUI *pCmdUI);

(don’t forget to #include the UndoStack.h file as well)

(不要忘记还包含UndoStack.h文件)

And in the SudokuView.cpp file add the following to the message map entries

然后在SudokuView.cpp文件中,将以下内容添加到消息映射项中

    ON_MESSAGE(SUD_PRESETVALUE, &CSudokuView::OnPreSetValue)
    ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CSudokuView::OnUpdateEditUndo)
    ON_COMMAND(ID_EDIT_UNDO, &CSudokuView::OnEditUndo)

The constructor also needs the following change to the parameter list (else the compiler will object about the CUndoStack object).

构造函数还需要对参数列表进行以下更改(否则编译器将以CUndoStack对象为对象)。

CSudokuView::CSudokuView()
    : CFormView(CSudokuView::IDD)
    , m_bInitialised(false)
    , m_stackUndo(SUD_UNDO)
{

And the following function definitions:

以及以下函数定义:

LRESULT CSudokuView::OnPreSetValue(WPARAM wParam, LPARAM lParam)
{
    m_stackUndo.AddItem((CWnd*)wParam, (int)lParam);
    return 0;
}

void CSudokuView::OnEditUndo()
{
    m_stackUndo.RemoveItem();
}

void CSudokuView::OnUpdateEditUndo(CCmdUI *pCmdUI)
{
    pCmdUI->Enable(m_stackUndo.HasItems());
}

And one final change, the OnUpdate function needs an extra line of code for the

最后一项更改是,OnUpdate函数需要为

case CSudokuDoc::eLoadGame: 
….
        //reset the undo stack
        m_stackUndo.Reset();
        Invalidate();
        break;

Now you should be able to compile and run and see the undo functionality in operation.  Note here we have added the message map entries by hand – simpler to use the wizard isn’t it.  However there are times when the wizard can't help and one needs to do it oneself.

现在,您应该能够编译和运行,并看到正在运行的撤消功能。 请注意,这里我们是手动添加消息映射条目的,不是更容易使用的向导。 但是,有时向导无法提供帮助,而您需要自己做。

结论: (Conclusion:)

We have implemented a simple undo facility via a class to represent a stack (LIFO = Last In First Out).

我们已经通过一个类实现了一个简单的撤消功能,以表示一个堆栈(LIFO =后进先出)。

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

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

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

There we found how to locate on disc where the running application file is located, how to generate and use random numbers and try...catch to handle exceptions so you code runs with crashing.

在那里,我们找到了如何在磁盘上找到正在运行的应用程序文件的位置,如何生成和使用随机数以及如何尝试... catch来处理异常,从而使代码运行时崩溃。

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

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

Here we will be working with a modal dialog, a multi-selection list and some more SQL code to maintain the database of games.

在这里,我们将使用模式对话框,多选列表和更多SQL代码来维护游戏数据库。

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

sudoku me

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值