Sudoku, a complete MFC application.  Part 9

AID: 3829
  • Status: Published

2540 points

  • ByAndyAinscow
  • TypeTutorial
  • Posted on2010-09-27 at 01:01:43

Introduction:


The undo support, implementing a stack.

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).

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:

#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;
};
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:

Select allOpen in new window



And the c.pp to be as follows

#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
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:

Select allOpen in new window



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.  

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.

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.  

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:

#define SUD_PRESETVALUE (WM_USER+103)
#define SUD_UNDO (WM_USER+104)
                                    
1:
2:

Select allOpen in new window



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

    afx_msg LRESULT OnUndo(WPARAM wParam, LPARAM lParam);
                                    
1:

Select allOpen in new window



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

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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen in new window



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

        if(iKey >= 0)
        {
            SetValue(iKey);
            return TRUE;
        }
                                    
1:
2:
3:
4:
5:

Select allOpen in new window



into:

        if(iKey >= 0)
        {
            GetParent()->SendMessage(SUD_PRESETVALUE, (WPARAM)this, (LPARAM)GetValue());
            SetValue(iKey);
            GetParent()->SendMessage(SUD_SETVALUE, (WPARAM)this, (LPARAM)iKey);
            return TRUE;
        }
                                    
1:
2:
3:
4:
5:
6:
7:

Select allOpen in new window



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:

    CUndoStack m_stackUndo;
    afx_msg LRESULT OnPreSetValue(WPARAM wParam, LPARAM lParam);
    afx_msg void OnEditUndo();
    afx_msg void OnUpdateEditUndo(CCmdUI *pCmdUI);
                                    
1:
2:
3:
4:

Select allOpen in new window



(don’t forget to #include the UndoStack.h file as well)
And in the SudokuView.cpp file add the following to the message map entries

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

Select allOpen in new window



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

CSudokuView::CSudokuView()
    : CFormView(CSudokuView::IDD)
    , m_bInitialised(false)
    , m_stackUndo(SUD_UNDO)
{
                                    
1:
2:
3:
4:
5:

Select allOpen in new window



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());
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

Select allOpen in new window



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

case CSudokuDoc::eLoadGame: 
….
        //reset the undo stack
        m_stackUndo.Reset();
        Invalidate();
        break;
                                    
1:
2:
3:
4:
5:
6:

Select allOpen in new window



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).



Click here for the source code for this article


Previous article in the series is here:  Sudoku in MFC: Part 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.

Next article in the series is here:  Sudoku in MFC: Part 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.


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.

Asked On
2010-09-27 at 01:01:43ID3829
Tags

MFC

Topic

Windows MFC Programming

Views
1210

Comments

Add your Comment

Please Sign up or Log in to comment on this article.

Join Experts Exchange Today

Gain Access to all our Tech Resources

Get personalized answers

Ask unlimited questions

Access Proven Solutions

Search 3.2 million solutions

Read In-Depth How-To Guides

1000+ articles, demos, & tips

Watch Step by Step Tutorials

Learn direct from top tech pros

And Much More!

Your complete tech resource

See Plans and Pricing

30-day free trial. Register in 60 seconds.

Loading Advertisement...

Top MFC Experts

  1. AndyAinscow

    3,970

    0 points yesterday

    Profile
    Rank: Genius
  2. mrwad99

    2,800

    0 points yesterday

    Profile
    Rank: Wizard
  3. JohnCz

    2,719

    0 points yesterday

    Profile
  4. VinExpert

    2,000

    0 points yesterday

    Profile
    Rank: Master
  5. sarabande

    1,668

    0 points yesterday

    Profile
    Rank: Sage
  6. jkr

    668

    0 points yesterday

    Profile
    Rank: Savant

Hall Of Fame