Sudoku, a complete MFC application.  Part 5

AndyAinscowFreelance programmer / Consultant
CERTIFIED EXPERT
Published:

Introduction:

Finishing the grid – keyboard support for arrow keys to manoeuvre, entering the numbers.  The PreTranslateMessage function is to be used to intercept and respond to keyboard events.

Continuing from the fourth article about sudoku.  

Open the project in visual studio.

If you run the app then you can manoeuvre from one button to the next along a row with the arrow keys (or the tab, shift+Tab) keys.  However the up/down arrow keys are just acting like the left/right arrow keys.  

Wouldn’t it be nice to move up and down simply with the arrow keys.

We need to override the PreTranslateMessage function in the CSudokuView to do that.  Class view, select class, right click for context menu, properties, then scroll down the override functions and <Add> PreTranslateMessage.

You should see this in the code window

BOOL CSudokuView::PreTranslateMessage(MSG* pMsg)
                      {
                          // TODO: Add your specialized code here and/or call the base class
                      
                          return CFormView::PreTranslateMessage(pMsg);
                      }

Open in new window


We need to add the following code where the TODO line currently is.

    if(pMsg->message == WM_KEYDOWN)
                          {
                              bool bMove = false;    //an arrow key - don't use default behaviour
                              int iFocus = 999;
                              switch(pMsg->wParam)
                              {
                              case VK_UP:
                                  iFocus = GetBtnFocused() - 9;    //'up' one row of the grid
                                  bMove = true;
                                  break;
                              case VK_DOWN:
                                  iFocus = GetBtnFocused() + 9;    //'down' one row of the grid
                                  bMove = true;
                                  break;
                              case VK_LEFT:
                                  iFocus = GetBtnFocused() - 1;    //'left' one column of the grid
                                  bMove = true;
                                  break;
                              case VK_RIGHT:
                                  iFocus = GetBtnFocused() + 1;    //'right' one column of the grid
                                  bMove = true;
                                  break;
                              }
                              if((iFocus >= 0) && (iFocus < 81))
                                  m_arWndButtons[iFocus].SetFocus();
                      
                              if(bMove)
                                  return TRUE;
                          }

Open in new window


Here the MSG structure contains various pieces of information.  We check if the message about to be processed is a key being pressed (WM_KEYDOWN) and if it is we then enter a block of code.  We set a flag to a default value of false and an int to a default of 999 (well away from any real value, there are only 81 buttons).  

Next the value of the wParam is inspected – this is the virtual key code for the key being pressed, we just want the arrow keys so they are the only options in the case blocks.  If an arrow key is being pressed we set the boolean flag to true – this will be used to exit the PreTranslateMessage prior to the base class (else one key press will result in two actions – not what we want).  

We also set the iFocus variable to the index of the button that currently has the focus and then adjust it to move within the row or column.  After the switch statement the iFocus is tested to be within the bounds of the grid (default is 999 also one could have for example been on the top row and tried to move up – not allowed) and if it is within the bounds then that button in the array is given the focus.  

Next the Boolean flag is tested, if it was an arrow key then we leave with the value TRUE – that informs the caller that we have handled the message (which we have done), a FALSE would result in the message being passed to another window for processing.

GetBtnFocused – that is also a function we have to write.

Add the following to the SudokuView.h file

protected:
                          int GetBtnFocused();

Open in new window


and to the SudokuView.cpp file add

int CSudokuView::GetBtnFocused()
                      {
                          CWnd* pFocus = GetFocus();
                          if(pFocus == NULL)
                              return 999;
                      
                          for(int i = 0; i < 81; i++)
                          {
                              if(&m_arWndButtons[i] == pFocus)
                                  return i;
                          }
                          return 999;
                      }

Open in new window


Here we get a pointer to the window that currently has the focus, then just loop through our array of buttons and compare.  If it matches then return the index of that button in the array else return a value out of bounds (eg. 999 - two places now that we use 999, maybe we should have used a #define).  Remember 0 is a valid member of the array, -1 is a typical value but we could increment the value returned by 9 – the end result is then not what we want (=bug).  

We have left the left/right arrow keys to move to previous/next row when it reaches the end of a row, the same as moving with a tab key would behave.  

Compile and run (F5 key) and you should be able to manoeuvre as if it really was a grid, try it.

If you wanted it would not be that much work to keep the loop within a row/column and have it able to loop continuously, eg. up from top row moves to bottom row…

Just a quick aside about PreTranslateMessage.  Windows is a message  based operating system, this function is called for each message that windows generates, not just keyboard, not just mouse – all.  It is a good place to slow your app down if you try to do too much here and if you write poor code.  Notice that for instance a VK_DOWN might have the same value as a message passed in a mouse action.  You will also get a VK_DOWN for both key press and key release actions.  Before the code checks for which key it was we first check if it was the message I want to handle.  (Agreed – the above code is not optimal for performance, but it is readable and the keyboard doesn’t generate messages as quickly as some other things.)

Now we will code the buttons responding to a numeric key press (and space bar as well).  Pressing a 1 through to 9 key will enter that value onto a button, a zero or space (nice big key on most keyboards) will remove it, anything else is to be ignored.  We could do this in the PreTranslateMessage of the view but it should not be of any concern to that window, so that doesn't seem the correct place for that piece of code.  The handling will be done in the button itself - well the key is being pressed when that button has the focus so we don't need any code to identify which grid cell is being modified.  So we will add a PreTranlateMessage handler to the CGridButton class and modify it to look like this:

BOOL CGridButton::PreTranslateMessage(MSG* pMsg)
                      {
                          if(!m_bLock && (pMsg->message == WM_KEYDOWN)) //if locked then can't change the value
                          {
                              int iKey = -1;
                      
                              switch(pMsg->wParam)
                              {
                              case VK_NUMPAD0:
                              case VK_NUMPAD1:
                              case VK_NUMPAD2:
                              case VK_NUMPAD3:
                              case VK_NUMPAD4:
                              case VK_NUMPAD5:
                              case VK_NUMPAD6:
                              case VK_NUMPAD7:
                              case VK_NUMPAD8:
                              case VK_NUMPAD9:
                                  iKey = (int)pMsg->wParam - VK_NUMPAD0;
                                  break;
                      
                              case '0':
                              case '1':
                              case '2':
                              case '3':
                              case '4':
                              case '5':
                              case '6':
                              case '7':
                              case '8':
                              case '9':
                                  iKey = (int)pMsg->wParam - '0';
                                  break;
                      
                              case VK_SPACE:
                              case VK_DELETE:
                              case VK_BACK:
                                  iKey = 0;
                                  break;
                              }
                              if(iKey >= 0)
                              {
                                  SetValue(iKey);
                                  GetParent()->SendMessage(SUD_SETVALUE, (WPARAM)this, (LPARAM)iKey);
                                  return TRUE;
                              }
                          }
                      
                      
                          return CButton::PreTranslateMessage(pMsg);
                      }

Open in new window


Notice we start to use the m_bLock variable, this wasn't explained earlier but this prevents you from replacing one of the ‘starting’ numbers of the game you are solving.  If the button is ‘locked’ then there is no point checking what key was pressed.  It is in other words flagging that this cell has a valid number already entered.

Also we see that there are two large groupings of case statements.  This is because the numeric keypad sends a different virtual keycode than the row of numbers above the letters on the keyboard (so you can check where the number being entered came from if you wished).

This code won’t compile.  Try it and you will the compiler complains about the SUD_SETVALUE.  In the gridButton.h add the following #define after the #pragma once

#define SUD_SETVALUE (WM_USER+101)

Open in new window


You should find it will now compile and run.  If you load the previously saved game then you should see the lock in action.  

What is the following line?  What does it do?  

GetParent()->SendMessage(SUD_SETVALUE, (WPARAM)this, (LPARAM)iKey);

Open in new window


First the buttons are controls on the formview, the GetParent returns a generic CWnd pointer to the parent.  We could cast (eg. static_cast) that to be a CSudokuView and call a member function directly but why do that.  In fact if we used this button on another form then it would result in crashes because the parent would not be CSudokuView type of window.  

The solution is simple, Windows is a message based operating system so send a message.  We have defined a value based on WM_USER (this is predefined by Microsoft so that any user message should not have the same value as a system message - ps. read about WM_USER and WM_APP in the help files and think about is this the correct one to use).  

Note that presently this message is being sent but nothing happens, not even an error!  The reason is simple, either a message is handled by a window or if no window has a handler then the message is thrown away and ignored.  This message will be handled in a later article.

We are also passing the ‘this’ pointer (an identification from which of the buttons the message came from) and also the new value of the button.  Both of those are being cast to the type of value the system expects in a SendMessage function.  These two parameters will be used in a later article.

Conclusion:

We have defined and used custom messages to pass information between window objects.
We have seen how to use the PreTranslateMessage function to respond to keyboard events by changing focus between controls.


Click here for the source code for this article


Previous article in the series is here:  Sudoku in MFC: Part 4
There we customised the status bar to provide information to the user about the current game.

Next article in the series is here:  Sudoku in MFC: Part 6
Here we will be implementing the owner drawing of the button class we have extended.  We will also be using a singleton object to maintain some application wide resources.


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,092 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.