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

sudoku me

介绍: (Introduction:)

整理网格–键盘支持操纵箭头的键,输入数字。 PreTranslateMessage函数用于拦截和响应键盘事件。

Continuing from the fourth article about sudoku.  

继续关于数独的第四篇文章。

Open the project in visual studio.

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

如果运行该应用程序,则可以使用箭头键(或选项卡,Shift + Tab)键沿一个行从一个按钮移至下一个按钮。 但是,向上/向下箭头键的作用类似于向左/向右箭头键。

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.

为此,我们需要覆盖CSudokuView中的PreTranslateMessage函数。 在“类”视图中,选择类,右键单击上下文菜单,属性,然后向下滚动覆盖功能和<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);
}

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

我们需要在T​​ODO行当前所在的位置添加以下代码。

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

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

这里的MSG结构包含各种信息。 我们检查要处理的消息是否为按下的键(WM_KEYDOWN),如果是,则输入代码块。 我们将标志设置为默认值false,将int设置为默认值999(与任何实际值相距甚远,只有81个按钮)。

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

接下来,检查wParam的值-这是所按下虚拟键代码 ,我们只需要箭头键,因此它们是case块中的唯一选项。 如果按下箭头键,则将boolean标志设置为true –这将用于在基类之前退出PreTranslateMessage(否则一键将导致两项操作–不是我们想要的操作)。

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.  

我们还将iFocus变量设置为当前具有焦点的按钮的索引,然后将其调整为在行或列内移动。 在switch语句之后,将iFocus测试为位于网格范围内(默认值为999,例如也可以将其放在第一行并尝试向上移动–不允许),如果它位于范围内,则该按钮数组中的焦点。

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.

接下来,将测试Boolean标志,如果它是箭头键,则我们使用值TRUE离开–通知调用方我们已经处理了消息(已完成),如果为FALSE,则消息将被传递到另一个窗口进行处理。

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

GetBtnFocused –这也是我们必须编写的函数。

Add the following to the SudokuView.h file

将以下内容添加到SudokuView.h文件中

protected:
    int GetBtnFocused();

and to the SudokuView.cpp file add

并添加到SudokuView.cpp文件

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

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

在这里,我们获得了指向当前具有焦点的窗口的指针,然后循环遍历按钮数组并进行比较。 如果匹配,则返回该按钮在数组中的索引,否则返回超出范围的值(例如999-现在我们使用999,这两个地方,也许我们应该使用#define)。 请记住,0是数组的有效成员,-1是一个典型值,但我们可以将返回的值加9 –最终结果不是我们想要的(= 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.  

当行到达行尾时,我们已经使用向左/向右箭头键移动到上一行/下一行,与使用Tab键移动的行为相同。

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

编译并运行(F5键),您应该可以操纵它,就像它确实是网格一样,请尝试一下。

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

快讲一下PreTranslateMessage。 Windows是基于消息的操作系统,此函数针对Windows生成的每条消息(不仅仅是键盘,不仅是鼠标)都被调用。 如果您在此处尝试执行过多操作并且编写了不良代码那么这是降低应用速度的好地方。 注意,例如,VK_DOWN可能与鼠标操作中传递的消息具有相同的值。 您还将为按键和释放按键操作获得VK_DOWN。 在代码检查哪个键之前,我们首先检查它是否是我要处理的消息。 (同意–上面的代码并非性能最佳,但可读性强,键盘生成消息的速度不如其他东西。)

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:

现在,我们将对响应数字按键(以及空格键)的按钮进行编码。 按下1到9键会将该值输入到按钮上,零或空格(大多数键盘上的大键)将其删除,其他任何情况都将被忽略。 我们可以在视图的PreTranslateMessage中执行此操作,但是它与该窗口无关,因此对于该段代码来说似乎不是正确的位置。 处理将在按钮本身中完成-当该按钮具有焦点时会按下该键,因此我们不需要任何代码来标识正在修改哪个网格单元。 因此,我们将向CGridButton类添加PreTranlateMessage处理程序,并将其修改为如下所示:

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

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.

请注意,我们开始使用m_bLock变量,之前没有进行解释,但这可以防止您替换要解决的游戏的“开始”编号之一。 如果按钮被“锁定”,那么就没有必要检查按下了什么键。 换句话说,标记此单元格具有已输入的有效数字。

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

我们还看到,case语句有两个大的分组。 这是因为数字小键盘发送的虚拟按键代码与键盘上字母上方的数字行不同(因此,您可以根据需要检查输入数字的来源)。

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

该代码将无法编译。 尝试一下,编译器会抱怨SUD_SETVALUE。 在gridButton.h中,在#pragma后面添加以下#define

#define SUD_SETVALUE (WM_USER+101)

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

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.  

首先,按钮是窗体视图上的控件,GetParent返回指向父级的通用CWnd指针。 我们可以将其转换为CSudokuView(例如static_cast),然后直接调用成员函数,但为什么要这样做。 实际上,如果我们在其他表单上使用此按钮,则将导致崩溃,因为父窗口不是CSudokuView类型的窗口。

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

解决方案很简单,Windows是基于消息的操作系统,因此可以发送消息。 我们基于WM_USER定义了一个值(这是Microsoft预先定义的,因此任何用户消息都不应具有与系统消息相同的值-ps。请阅读帮助文件中有关WM_USER和WM_APP的信息,并考虑这是否是正确的信息)。用)。

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.

我们还将传递“ this”指针(标识消息来自哪个按钮的标识)以及按钮的新值。 这两个值都被强制转换为系统在SendMessage函数中期望的值类型。 这两个参数将在以后的文章中使用。

结论: (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.

我们已经看到了如何使用PreTranslateMessage函数通过更改控件之间的焦点来响应键盘事件。

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

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

该系列中的上一篇文章在这里: MFC中的Sudoku:第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

该系列的下一篇文章在这里: MFC中的Sudoku:第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.

我将做一些事情来说明一个观点–不能认为它是“最佳”实践,实际上,另一种选择可能更简单,更合适。 代码中的某些要点甚至被称为不良设计和可能的错误源。

翻译自: https://www.experts-exchange.com/articles/3824/Sudoku-a-complete-MFC-application-Part-5.html

sudoku me

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值