Sudoku, a complete MFC application.  Part 6

AndyAinscowFreelance programmer / Consultant
CERTIFIED EXPERT
Published:

Introduction:

Ownerdraw of the grid button.  A singleton class implentation and usage.

Continuing from the fifth article about sudoku.  

Open the project in visual studio.

Go to the class view – CGridButton should be visible as a class.  Right click and go to the properties.  As in the first article click the button to display the overrides, scroll down to PreSubclassWindow and add a function in the combo box.  The editor window should have GridButton.cpp opened and the following code there:

void CGridButton::PreSubclassWindow()
                      {
                          // TODO: Add your specialized code here and/or call the base class
                          CButton::PreSubclassWindow();
                      }

Open in new window


Change it to the following

void CGridButton::PreSubclassWindow()
                      {
                          ModifyStyle(NULL, BS_OWNERDRAW);
                      }

Open in new window


Notice we don’t need to call the base class implementation (if you looked at it you find it does nothing).  

A quick aside.  Leave the call to the base class there, put the cursor on that line and press the F9 key.  You should see a red dot to the far left of that line.  Now compile and run and the program should stop executing with a yellow arrow now in the red button.  Press the F11 key (step into) and you see the code in that function – does nothing it says so there.

The ModifyStyle – the first parameter is styles to be removed, we don’t want any removing, the second is styles to add.  We want an owner draw button so we add the style flag to indicate owner draw – BS_OWNERDRAW.

An owner draw crash of the application, a simple debug exercise.  The program now compiles but will crash in running – try it.  Sudoku.exe has triggered a breakpoint.  Now press the continue button.  The message appears again but on screen another file is displayed – I have winctrl1.cpp.  Now press the break button on the message box.  You should see a yellow arrow pointing at a line ASSERT(FALSE);  This is the complete code

// Derived class is responsible for implementing all of these handlers
                      //   for owner/self draw controls
                      void CButton::DrawItem(LPDRAWITEMSTRUCT)
                      {
                          ASSERT(FALSE);
                      }

Open in new window


The source of the crash is located and the comment gives us a pretty strong hint of what we are doing wrong.  Namely the button is now owner drawn but we have not done any code to draw it.  

Now repeat the steps for adding the PreSubclassWindow but this time select and add a DrawItem handler.  (It will now compile and run without error but we see no buttons – owner draw means exactly that, we must now draw everything – colours, fonts, dynamic pictures – the display is now ours).

This application is only going to use a fairly simple display of the button – none of the themed effects that would be possible.  To display the text of the button we will require a font, some sort of border would be useful and a visual hint as feedback that the button has the focus.  

For the font it could be possible to create it everytime the button needs to be redrawn.  Does that slow things down?  If we had the font as a member variable of the CGridButton class then it would have to be created once.  However we have 81 instances of that class – so 81 individual copies of the font each taking a limited resource up.  (Each would have a ‘HANDLE’ and there is a limited number of these available to the operating system, admittedly a lot more than were available on the 16 bit versions of windows).  Can we just make one copy of the font and share it?  One solution would be to have it as a member of the application, here we will develop an alternative – a singleton class.

From the ‘solution’ view right click Sudoku and choose Add then Class from the context menu.  From the Wizard choose C++ in the tree (we used MFC previously for the button, but there isn’t a singleton base class in MFC), C++ class from the installed templates at the right, then click the Add button.  For the class name enter CUIElements (User Interface Elements) then click Finish.

In the code window should be the file UIElements.h with the following contents:

#pragma once
                      
                      class CUIElements
                      {
                      public:
                          CUIElements(void);
                          ~CUIElements(void);
                      };

Open in new window


This is going to be a simple singleton class not designed to cope with multi threading issues.  The code should be modified so it is now this:

class CUIElements
                      {
                      private:
                          CUIElements() {};
                          CUIElements(const CUIElements&) {};
                          CUIElements& operator=(const CUIElements&) { return Instance(); };
                          ~CUIElements() {};
                      
                          CFont m_fntNumber;
                      
                      public:
                          static CUIElements& Instance();
                          const CFont& GetNumberFont();
                      };

Open in new window


This is an implementation of a Meyers (Scott Meyers) singleton.  

Notice first the constructor is private, as are copy constructor and assignment operators, even the destructor.  This prevents them from being called outside the class and from derving a new class based on this.  What an odd looking class this is.  How can we create an object of this sort?  

The clue is in the public members, namely the static member function called Instance – that returns a reference to an object of this sort.  A reference is preferable to a pointer - one can attempt to delete a pointer for instance.  Notice we are really trying to make certain only one instance can be created external to the class and that it can't be destroyed externally.

Now open the UIElements.cpp file and add the following code there:

CUIElements& CUIElements::Instance()
                      {
                          //static means it is not to be destroyed once the function exits, to be shared across all instances of this class
                          static CUIElements inst;
                          return inst;
                      }
                      
                      const CFont& CUIElements::GetNumberFont() 
                      { 
                          if(m_fntNumber.GetSafeHandle() == NULL)
                          {
                              //font not yet created - create it now
                      
                              //Obtain a DC (Device Context) for the screen
                              HDC hDC = ::GetDC(NULL);
                              int iLogPixelsY = GetDeviceCaps(hDC, LOGPIXELSY);
                              ::ReleaseDC(NULL, hDC);
                      
                              LOGFONT logFont;
                              memset( &logFont, 0, sizeof(logFont));
                      
                              //24 point bold, arial font
                              _tcscpy_s(logFont.lfFaceName, LF_FACESIZE, _T("Arial"));
                              logFont.lfHeight = -MulDiv(24, iLogPixelsY, 72);    
                              logFont.lfWeight = FW_BOLD;
                              logFont.lfUnderline = false;
                              logFont.lfItalic = false;
                              m_fntNumber.CreateFontIndirect(&logFont);
                          }
                      
                          //Safety - check it has been created
                          ASSERT(m_fntNumber.GetSafeHandle()); 
                          return m_fntNumber; 
                      }

Open in new window


First we see the static member function ‘Instance’ has a static member variable – ahhh it is a CUIElement.  Now a static member is shared across all instances of a class (not local to one object), also it is not destroyed when the function exits.  

As mentioned this is not a thread safe technique but it is satisfactory for our application and simple to code.  

Why is it not thread safe?  In theory one could have two threads which require the singleton object.  The very first call to the Instance function is not thread safe because the first thread through the path creates the object. If the second thread then tries to go through the Instance function at the same time there is a race condition as to which will create the instance object and it is possible that one of them may slip through and return a reference to the instance before it has actually been created.

There are some methods to stop this race condition happening, a simple one is to make sure it is called at least once in the main thread before another thread could access it. This will ensure the instance object is created before multiple threads try to access the object. The function itself is completely thread safe after the initial call.

We also have a member variable which is a CFont type of variable.  Typical of many MFC objects it has a two stage creation.  If the Handle associated with it is NULL then it is not created completely – test it and create it if required.  We require information about the number of pixels for an inch of screen real estate, this we use in determining the height of the font.  We first fill in a LOGFONT structure with the information about the font we require then we create the font indirectly based on this information.  (Note the app assumes this will succeed – we really ought to have code to return a font if our custom font creation fails).

As there is only ever one instance of the singleton then this font is only created once.

Back to the button:  We must draw everything so we need to draw the frame and contents of the button.  A visual hint that the button is focussed is important (else how can the user know which grid element is active).  Maybe one should also display the ‘locked’ buttons.  We will also display an ‘invalid’ number as highlighted (eg. 7 appears twice in a row – not a correct solution)

Now modify the DrawItem code so it is as follows:

void CGridButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
                      {
                          ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);
                      
                          //Get an instance of the singleton - for the font
                          CUIElements& instUI = CUIElements::Instance();
                      
                          //Get the button text - if any to display
                          CString szText;
                          if(GetValue() > 0)
                              szText.Format("%d", GetValue());
                      
                          //Obtain an MFC device context - less typing for the next steps
                          CDC dc;
                          dc.Attach(lpDrawItemStruct->hDC);
                      
                          //Perform a save of the state, we will be manipulating it, this allows one line of code to restore the state later
                          int iSaveDC = dc.SaveDC();
                      
                          //Draw a simple rectangle, if button has the focus then draw another just inside the first
                          CRect rc(lpDrawItemStruct->rcItem);
                          if (lpDrawItemStruct->itemState & ODS_FOCUS)
                          {
                              dc.Rectangle(&rc);
                              rc.DeflateRect(1, 1);
                              dc.Rectangle(&rc);
                          }
                          else
                              dc.Rectangle(rc.left, rc.top, rc.right, rc.bottom);
                      
                          //if there is a number to display then we display it
                          if(GetValue() > 0)
                          {
                              //Display a locked cell in a different colour
                              if(m_bLock)
                              {
                                  rc.DeflateRect(1, 1);
                                  dc.FillSolidRect(&rc, m_clrLockedCell);
                              }
                      
                              dc.SelectObject(instUI.GetNumberFont());
                      
                              // Draw the button text using the text color blue (or red - if invalid and grid is filled)
                              COLORREF clr = (m_bValid ? RGB(0,0,255) : RGB(255,0,0));
                              dc.SetTextColor(clr);
                      
                              //If locked then we need to set the background colour - else it looks ghastly
                              if(m_bLock)
                                  dc.SetBkColor(m_clrLockedCell);
                              dc.DrawText(szText, &rc, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
                          }
                          else
                          {
                              //Draw hint
                          }
                      
                          //restore the state of the dc to the original
                          dc.RestoreDC(iSaveDC);
                          //We don't require the dc any more, release it
                          dc.Detach();
                      }

Open in new window


We need a couple of lines to be added to the header file (GridButton.h).

private:
                          bool m_bValid;
                          static const COLORREF m_clrLockedCell;
                      public:
                          void SetValid(bool bFlag) { m_bValid = bFlag; };

Open in new window


This declares two variables we used in the DrawItem and also a new function for setting the validity state of the number entered into the GridButton.

In the GridButton.cpp file we need to add an #include “UIElements.h” to the top of the file like this:

#include "GridButton.h"
                      #include "UIElements.h"
                      const COLORREF CGridButton::m_clrLockedCell = RGB(192, 192, 192);

Open in new window


The static variable for the colour of a locked cell is done as shown in the previous line of code (Note it is not in the constructor because this is a const shared over all instances of the CGridButton class).

To the initialisation list at the constructor we set the variable controlling the validity to a default value of true – it should appear as follows:

,    m_bLock(false)
                      ,    m_bValid(true)

Open in new window


Finally we need to make certain that this ‘valid’ flag is reset to true when we load a game, so in the CSudokuView::OnUpdate we need an extra line of code as we load the grid contents:

                m_arWndButtons[row * 9 + col].SetLock();
                                      m_arWndButtons[row * 9 + col].SetValid(true);

Open in new window


Now you can press F5 to compile and run.  Load the previously saved game and see how it now looks and behaves.  

Conclusion:

We have coded owner drawing of a window object (button).
We have implemented a singleton class to provide one copy of a font that can be used anywhere in the application.


Click here for the source code for this article


Previous article in the series is here:  Sudoku in MFC: Part 5
There we used the PreTranslateMessage function to provide keyboard support to navigate the grid.  We also implemented keyboard entry of the numbers when on a cell in the grid.

Next article in the series is here:  Sudoku in MFC: Part 7
Here we will be implementing hints to help in solving a game.  We will also meet a class nested inside another and some simple debugging to find and cure a bug in the code.


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
4,254 Views
AndyAinscowFreelance programmer / Consultant
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.