<

Expiring Today—Celebrate National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x

Sudoku, a complete MFC application.  Part 3

Published on
10,087 Points
2,987 Views
1 Endorsement
Last Modified:

Introduction:

Load and Save to file, Document-View interaction inside the SDI.

Continuing from the second article about sudoku.  

Open the project in 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:
private:
    char m_arGame[82];
public:
    enum {eLoadGame, eSaveGame};
    char* GetGame() { return m_arGame; };

Open in new window


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.

From the class view navigate to the OnNewDocument function of CSudokuDoc, it should be as follows:
BOOL CSudokuDoc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

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

    return TRUE;
}

Open in new window


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;

Open in new window


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.

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

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;
    }
}

Open in new window


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:

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.  

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.

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)

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.

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

CButton m_arWndButtons[81];

Open in new window


To

CGridButton m_arWndButtons[81];

Open in new window


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:

#pragma once
#include "GridButton.h"

Open in new window


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

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

class CGridButton : public CButton
{
    DECLARE_DYNAMIC(CGridButton)

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

protected:
    DECLARE_MESSAGE_MAP()
};

Open in new window


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
};

Open in new window


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

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

Open in new window


Also we ought to initialise the variables we have declared:

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

Open in new window


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.  

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.

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

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

Open in new window


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);
    }
}

Open in new window


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…

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:

private:
    CString m_szInfo;
    char m_arGame[82];

Open in new window


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:

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

Open in new window


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
There we looked at dynamically positioning controls on a window from code.  We also used the windows registry for storing information about the application.


Next article in the series is here:  Sudoku in MFC: Part 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.
1
Comment
Author:AndyAinscow
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
0 Comments

Featured Post

Moving data to the cloud? Find out if you’re ready

Before moving to the cloud, it is important to carefully define your db needs, plan for the migration & understand prod. environment. This wp explains how to define what you need from a cloud provider, plan for the migration & what putting a cloud solution into practice entails.

Join & Write a Comment

This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month