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

sudoku me

介绍: (Introduction:)

对话框(1)模态-维护数据库。

Continuing from the ninth article about sudoku.  

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

You might have heard of modal and modeless dialogs.  Here with this Sudoku application will we use one of each type: a modal dialog to help with maintaining the database and a modeless dialog for helping ‘solve’ a game.  The solving procedure will be the subject of the next article so we will just concentrate on the modal dialog and the database maintenance in this article.

您可能听说过模态和无模态对话框。 在此Sudoku应用程序中,我们将使用每种类型中的一种:用于帮助维护数据库的模式对话框和用于帮助“解决”游戏的无模式对话框。 解决过程将是下一篇文章的主题,因此在本文中我们将仅关注模式对话框和数据库维护。

Add a new dialog resource in the resource editor and change the ID to IDD_DLG_MAINTAIN and the caption to Maintenance (properties of the dialog).  Then add a list control, three buttons and a picture control.  Select the list control and in the properties make certain ‘Always Show Selection’ is true, ‘No Sort Header’ is true, ‘Single Selection’ is false, ‘Sort’ is none and ‘View’ is report.  Select the first button, change the ID to IDC_BUTTON_EXPORT and the caption to Export.  The second button has the ID to be IDC_BUTTON_IMPORT and the caption as Import.  The third button is IDC_BUTTON_DELETE and Delete as the caption.  The picture control we have an ID of IDC_STATIC_BOUNDARY and make certain the ‘Type’ is Frame.

在资源编辑器中添加一个新的对话框资源,并将ID更改为IDD_DLG_MAINTAIN,将标题更改为Maintenance(对话框的属性)。 然后添加一个列表控件,三个按钮和一个图片控件。 选择列表控件,并在属性中确保“始终显示选择”为true,“无排序标题”为true,“单个选择”为false,“排序”为无且“视图”为报告。 选择第一个按钮,将ID更改为IDC_BUTTON_EXPORT,将标题更改为Export。 第二个按钮的ID为IDC_BUTTON_IMPORT,标题为Import。 第三个按钮是IDC_BUTTON_DELETE,然后单击Delete作为标题。 图片控件的ID为IDC_STATIC_BOUNDARY,并确保“类型”为帧。

Resource editor

You can use the buttons in the resource editor to help align (all four sides) and position controls.  The picture above is how I have the dialog layout in the resource editor.  Also delete the two buttons (OK and Cancel) that were on the default dialog resource template - we don't need them.

您可以使用资源编辑器中的按钮来帮助对齐(所有四个侧面)和位置控件。 上图是我在资源编辑器中的对话框布局。 还要删除默认对话框资源模板上的两个按钮(确定和取消)-我们不需要它们。

Now right click on the dialog background (NOT a control) and select add new class.  We require an MFC based class with the name CDlgMaintain and select CDialog as the base class.  In the resource editor open the menu we have for the application, select the edit point then the ‘Database Maintenance’ sub menu item.  Now right click and ‘Add Event Handler’.  We want to add a command handler to the CSudokuDoc class.

现在,右键单击对话框背景(不是控件),然后选择添加新类。 我们需要一个名称为CDlgMaintain的基于MFC的类,并选择CDialog作为基类。 在资源编辑器中,打开应用程序所需的菜单,选择编辑点,然后选择“数据库维护”子菜单项。 现在,右键单击并单击“添加事件处理程序”。 我们要向CSudokuDoc类添加命令处理程序。

void CSudokuDoc::OnEditDatabasemaintenance()
{
    //Import and Export to database, remove unwanted entries from the database
    CDlgMaintain dlg(m_pDB);
    dlg.DoModal();
}

We also require the #include “DlgMaintain.h” at the top of the file (otherwise it won’t compile).

我们还需要文件顶部的#include“ DlgMaintain.h”(否则它将无法编译)。

Note the dlg(m_pDB) as we declare the variable.  Somehow we need to pass a pointer to the database to this dialog so that it can perform operations on the database.  I have made a custom constructor for the dialog class which requires a pointer to the database.  This can be useful in that it reduces the risk of forgetting to set the database pointer in a later line of code after declaring a variable of this type.  The following is the code we need to add in the DlgMaintain.cpp / .h files to the wizard generated code:

在声明变量时请注意dlg(m_pDB)。 我们需要以某种方式将指向数据库的指针传递给此对话框,以便它可以对数据库执行操作。 我为对话框类制作了一个自定义构造函数,它需要一个指向数据库的指针。 这很有用,因为它减少了在声明此类型的变量之后忘记在后面的代码行中设置数据库指针的风险。 以下是我们需要在DlgMaintain.cpp / .h文件中添加到向导生成的代码的代码:

#pragma once
#include "afxcmn.h"
#include "afxdao.h"
#pragma warning(disable : 4995)
…
    //CDlgMaintain(CWnd* pParent = NULL);   //Remove the standard code, we do NOT allow this
    CDlgMaintain(CDaoDatabase* pDB, CWnd* pParent = NULL);   
…
private:
    CDaoDatabase* m_pDB;
public:
    virtual void OnOK() {};

And in .cpp file

并在.cpp文件中

CDlgMaintain::CDlgMaintain(CDaoDatabase* pDB, CWnd* pParent /*=NULL*/)
    : CDialog(CDlgMaintain::IDD, pParent)
    , m_pDB(pDB)
{
}

The application should now compile and the maintenance dialog is available.  Try pressing the return key on the keyboard – nothing happens – that is because of the virtual function OnOK.  The default behaviour of the dialog is to respond to the return key by running the OnOK function.  The base class implementation will dismiss the dialog – this line of code stops that happening.

现在应该可以编译该应用程序,并且可以使用维护对话框。 尝试按键盘上的回车键-没有任何React-这是由于虚拟功能OnOK所致。 对话框的默认行为是通过运行OnOK函数来响应返回键。 基类实现将关闭该对话框–此代码行阻止了这种情况的发生。

Now we need to get things implemented in it.  From the resource editor right click the mouse on the list control and add a variable.  We want a control type variable (CListCtrl) and call it m_wndList – make it a private variable, no other class should require access to this.  In the header declare two new private functions – PrepareColumnHeaders, no parameters and returning void and AddData, no parameters and also returning void.

现在我们需要在其中实现一些东西。 在资源编辑器中,右键单击列表控件上的鼠标,然后添加一个变量。 我们需要一个控件类型变量(CListCtrl)并将其命名为m_wndList-将其设为私有变量,其他任何类都不需要对此进行访问。 在标头中声明两个新的私有函数– PrepareColumnHeaders,无参数,返回void,AddData,无参数,也返回void。

private:
    CDaoDatabase* m_pDB;
    CListCtrl m_wndList;
    void PrepareColumnHeaders();
    void AddData();

Modify the OnInitDialog function (if it isn’t there then use the properties window of the dialog from the class window view to select the overrides and add the override for OnInitDialog).

修改OnInitDialog函数(如果不存在,则从类窗口视图中使用对话框的属性窗口来选择替代并为OnInitDialog添加替代)。

BOOL CDlgMaintain::OnInitDialog()
{
    CDialog::OnInitDialog();

    PrepareColumnHeaders();    //Columns into the list control
    AddData();    //Fill from the database

    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

void CDlgMaintain::PrepareColumnHeaders()
{
    CRect rect;
    m_wndList.GetClientRect(&rect);

    //Width of control MINUS one fixed width column plus space for vertical scroll bar
    int cx = rect.Width() - 40 - GetSystemMetrics(SM_CXVSCROLL);    

    m_wndList.InsertColumn(0, "ID", LVCFMT_LEFT, 40);
    m_wndList.InsertColumn(1, "Game", LVCFMT_LEFT, cx);    //Fills the control with this column
}

void CDlgMaintain::AddData()
{
    //Remove any entries first
    m_wndList.DeleteAllItems();

    CDaoRecordset RS(m_pDB);
    RS.Open(AFX_DAO_USE_DEFAULT_TYPE, "SELECT ID, GameDetail FROM Games ORDER BY ID");

    //Just in case there are no games in the database - nothing to put in the list control
    if(RS.IsBOF() && RS.IsEOF())
        return;

    RS.MoveFirst();
    UINT nItem = -1;
    while(!RS.IsEOF())
    {
        LVITEM lvItem = {0};    //Set all members to be zero initially
        lvItem.mask = LVIF_TEXT;
        lvItem.iItem = nItem;

        //ID of the item in the DB, put into a string
        COleVariant var = RS.GetFieldValue("ID");
        CString szID;
        szID.Format("%ld", var.lVal);
        lvItem.pszText = szID.GetBuffer();

        //nItem is the index of the newly added item
        nItem = m_wndList.InsertItem(&lvItem);

        //Now the game 'details'
        var = RS.GetFieldValue("GameDetail");
        //It is a string in the table, now convert it to a string we can use
        CString szGame(var.pbVal);
        m_wndList.SetItemText(nItem, 1, szGame.GetBuffer());

        //Next record and increment the positioning counter for the InsertItem (so appears at end of the list control)
        RS.MoveNext();
        nItem++;
    }

    //If there are any items then select the first in the list
    if(nItem >= 0)
        m_wndList.SetItemState(0, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
    
    RS.Close();
}

If you compile and run you should see a picture similar to the following when you select Database Maintenance from the edit menu.

如果编译并运行,当从编辑菜单中选择“数据库维护”时,应该会看到类似以下的图片。

List view with games in database

Obvious how the game is laid out in the grid isn’t it (well, not really, do you understand the string of digits? ).  This is what we have the frame underneath the list control for.  Inside that frame we will display the selected game, but only if one and one only is selected in the list control.  Add a new class to the project form the class view.  It is a C++ (not MFC) class and name it CSudokuDCPainter.  This will handle the painting of the game inside this rectangle – we are delegating the painting to another class because we will also require a game to be displayed in the solve dialog (code reuse).

显然游戏是如何布置在网格中的(不是,不是真的,您是否理解数字串?)。 这就是列表控件下方的框架。 在该框架内,我们将显示选定的游戏,但

class CSudokuDCPainter
{
public:
    CSudokuDCPainter(void) {};
    ~CSudokuDCPainter(void) {};
    void Paint(CDialog* pDlg, CDC* pDC, UINT nBoundary, CString szGame);
};

And

void CSudokuDCPainter::Paint(CDialog* pDlg, CDC* pDC, UINT nBoundary, CString szGame)
{
    //We have a block of code in the view - with a copy/paste 
    //we can use that with only minor changes here (adding a pDlg->)
    //for drawing the cell boundaries.  

    CRect rcFrame;
    pDlg->GetDlgItem(nBoundary)->GetWindowRect(&rcFrame);    //Get where it is to be drawn

    pDlg->ScreenToClient(&rcFrame);
    int iOffsetX = rcFrame.Width() / 3;
    int iOffsetY = rcFrame.Height() / 3;

    pDC->MoveTo(rcFrame.left + iOffsetX, rcFrame.top);
    pDC->LineTo(rcFrame.left + iOffsetX, rcFrame.bottom);

    pDC->MoveTo(rcFrame.left + 2*iOffsetX, rcFrame.top);
    pDC->LineTo(rcFrame.left + 2*iOffsetX, rcFrame.bottom);

    pDC->MoveTo(rcFrame.left, rcFrame.top + iOffsetY);
    pDC->LineTo(rcFrame.right, rcFrame.top + iOffsetY);

    pDC->MoveTo(rcFrame.left, rcFrame.top + 2*iOffsetY);
    pDC->LineTo(rcFrame.right, rcFrame.top + 2*iOffsetY);

    //Do we have a game to paint?
    if(szGame.IsEmpty())
        return;

    //Now split into the 'digits' for display - only digits 1..9
    //We did something similar for the hints in the grid button DrawItem routine

    //We have an iOffsetX & Y for the 3x3 grid spacers - However we now require 9 rows
    //so recalculate a miini offset inside the larger offsets
    int iOffsetXmini = iOffsetX / 3;
    int iOffsetYmini = iOffsetY / 3;

    CRect rcClient;
    CString s;

    //Otherwise the digits appear with a backgound colour
    pDC->SetBkMode(TRANSPARENT);

    //Use the same font as the dialog, rather than the default font in the DC
    CFont* pOldFont = pDC->SelectObject(pDlg->GetFont());

    for(int iRow = 0; iRow < 9; iRow++)
    {
        //every third row reset to the internal spacers 
        //(remember integral math for the division, box might not be multiple of 9)
        if((iRow % 3) == 0)
            rcClient.top = rcFrame.top + (iRow / 3) * iOffsetY;
        else
            rcClient.top += iOffsetYmini;

        rcClient.bottom = rcClient.top + iOffsetYmini;

        for(int iCol = 0; iCol < 9; iCol++)
        {
            if((iCol % 3) == 0)
                rcClient.left = rcFrame.left + (iCol / 3) * iOffsetX;
            else
                rcClient.left += iOffsetXmini;

            rcClient.right = rcClient.left + iOffsetXmini;

            s = szGame[iRow*9 + iCol];

            //Only display if non zero
            if(s.Compare("0") != 0)
                pDC->DrawText(s, s.GetLength(), &rcClient, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
        }
    }

    //reset the dc to the original - it might be reused elsewhere
    pDC->SelectObject(pOldFont);
}

Nothing particularly special in the code above so I am not going to add any more description to it.  Is there a reason we drew the frame directly in the view but here used a helper class?  Basically, yes there is - just to make the explanation simpler in the earlier article.  Note that as a result we now have a poorer design because code is being duplicated.

上面的代码没有什么特别的,因此我将不对其添加任何描述。 我们是否有理由直接在视图中绘制框架,但是这里使用了辅助类? 基本上,是的-只是为了使上一篇文章中的解释更简单。 注意,结果是我们现在的设计较差,因为代码被重复了。

Now we need to modify the OnPaint function of the Maintenance dialog (if it doesn’t exist add a handler for the WM_PAINT message via properties of the class view – we’ve done similar tasks in the earlier articles) as follows:

现在,我们需要修改“维护”对话框的OnPaint函数(如果不存在,请通过类视图的属性为WM_PAINT消息添加处理程序–我们在之前的文章中已经完成了类似的任务),如下所示:

void CDlgMaintain::OnPaint()
{
    CPaintDC dc(this); // device context for painting
    // TODO: Add your message handler code here
    // Do not call CDialog::OnPaint() for painting messages

    CString szGame;

    //Get the selected game (if any, and also if only one selected) for painting onto this dialog
    POSITION pos = m_wndList.GetFirstSelectedItemPosition();
    if(pos != NULL) //Is something selected in the list
    {
        //Get some info about the selected item
        int iIndex = m_wndList.GetNextSelectedItem(pos);

        if(pos == NULL)    //Must only be one selected in the list control
        {
            //Get the game details from the list control
            szGame = m_wndList.GetItemText(iIndex, 1);
        }
    }

    CSudokuDCPainter painter;
    painter.Paint(this, &dc, IDC_STATIC_BOUNDARY, szGame);
}

Here we have a multi-selection list control.  Using the GetFirstSelectedItemPosition and GetNextSelectedItem functions is how one can iterate through the items that have been selected in a list control.  If you compile and build you should see something like the following:  (Note if you make a multiple selection of items in the list then the grid is empty).

在这里,我们有一个多选列表控件。 使用GetFirstSelectedItemPositi on和GetNextSelectedItem函数是如何迭代列表控件中已选择的项目的方法。 如果进行编译和构建,您应该会看到类似以下内容的内容:(请注意,如果您在列表中进行了多项选择,则网格为空)。

Maintenance dialog

Unfortunately if you change the selection in the list control then the display of the grid is not updated to reflect that.  We need to force the grid to be updated when the selection in the list is changed.  So in the resource editor select view this maintenance dialog template and select the list control then view the properties.  Click the ‘control events’ button and add a new function for the LVN_ITEMCHANGED event.  The auto generated function will be as follows:

不幸的是,如果您在列表控件中更改选择,则网格的显示不会更新以反映这一点。 当列表中的选择更改时,我们需要强制更新网格。 因此,在资源编辑器中,选择“查看此维护对话框模板”,然后选择列表控件,然后查看属性。 单击“控制事件”按钮,并为LVN_ITEMCHANGED事件添加新功能。 自动生成的功能如下:

void CDlgMaintain::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    // TODO: Add your control notification handler code here
    *pResult = 0;
}

We need to add an extra line of code before the function exits.  Simplest (but not the most efficient) is

在函数退出之前,我们需要添加一行额外的代码。 最简单(但不是最有效)是

void CDlgMaintain::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
    // TODO: Add your control notification handler code here
    *pResult = 0;

    //Force a redraw - for the details of the game
    Invalidate();
}

Compile and test it – it should update the grid as you change selections in the list.  Why isn’t it efficient?  The complete dialog client area is being redrawn, including elements (buttons, list) that have not been changed.  So instead of Invalidate one can use the following to invalidate just a section of the dialog.  However for this to work efficiently in the OnPaint (or OnDraw) one needs to find just what area has been changed and just redraw that area.

编译和测试它–在更改列表中的选择时,它应该更新网格。 为什么效率不高? 正在重绘完整的对话框客户区域,包括尚未更改的元素(按钮,列表)。 因此,可以使用以下内容代替对话框中的一部分来使它们无效。 但是,为了使它在OnPaint(或OnDraw)中有效地工作,您需要找出已更改的区域并重新绘制该区域。

    //Force a redraw - for the details of the game
    CRect rc;
    GetDlgItem(IDC_STATIC_BOUNDARY)->GetWindowRect(&rc);
    ScreenToClient(&rc);
    InvalidateRect(rc);

We have to code the actual database maintenance now, the visual effects are finished.  Add handlers for the button clicks for the Delete, Export and Import buttons using the wizard (resource editor – context menu – add event handler).

现在,我们必须对实际的数据库维护进行编码,视觉效果已完成。 使用向导为按钮单击“删除”,“导出”和“导入”添加处理程序(资源编辑器–上下文菜单–添加事件处理程序)。

Maybe one can reduce the amount of duplicate code in the following (in some respects both delete and export are similar in the looping) but I’m going to duplicate code for simplicity.

也许可以在下面减少重复代码的数量(在某些方面,循环中的删除和导出都相似),但是为了简单起见,我将重复代码。

To delete, we need to get the ID’s of the items to delete and then remove them from the database.  I’ll use an SQL statement to perform the deletion.  

要删除,我们需要获取要删除的项目的ID,然后将其从数据库中删除。 我将使用SQL语句执行删除。

void CDlgMaintain::OnBnClickedButtonDelete()
{
    //Get the selected game if any, exit routine if nothing selected
    POSITION pos = m_wndList.GetFirstSelectedItemPosition();
    if(pos == NULL)
        return;

    //Ask for confirmation - there is no undo
    if(AfxMessageBox(IDS_QRY_DELETE_GAMES, MB_YESNO | MB_ICONQUESTION) != IDYES)
        return;

    int iIndex;
    CString s, szRecordKeys;
    while(pos != NULL) //Is something selected in the list
    {
        iIndex = m_wndList.GetNextSelectedItem(pos);
        s = m_wndList.GetItemText(iIndex, 0);    //The ID of the game selected
        szRecordKeys += s + _T(",");    //Add the index and append a comma
    }

    //Remove the final comma from the string containing the keys
    szRecordKeys.TrimRight(_T(','));

    //Prepare the delete command
    s.Format(_T("DELETE * FROM Games WHERE ([ID] IN (%s))"), szRecordKeys);
    
    //Run the delete command
    m_pDB->Execute(s);

    //Refill the list and redisplay
    AddData();
    Invalidate();
}

Note that I have added a resource into the string table in the resource editor to provide the text for confirming the delete.  In theory there is a minor problem with this code.  The SQL statement – there is a limit on how many characters it can contain so in a production environment one ought to check for that and perform the action in a loop if the size is overrun.

请注意,我已经在资源编辑器的字符串表中添加了资源,以提供用于确认删除的文本。 从理论上讲,此代码存在一个小问题。 SQL语句–可以包含多少个字符是有限制的,因此在生产环境中,应该检查该字符并在大小超出限制的情况下循环执行操作。

The export operation is next, we want to write out to a file that will be ‘understood’ by the import operation.  Basically all we require is the game to be exported, each game on a new line in the file.  We need to prompt for a file name (and location) but most of this work is done for us in a common dialog – the CFileDialog class in MFC.

接下来是导出操作,我们要写出一个将被导入操作“理解”的文件。 基本上,我们所需要做的就是导出游戏,每个游戏都放在文件的新行中。 我们需要提示输入文件名(和位置),但是大部分工作是在一个通用对话框(MFC中的CFileDialog类)中完成的。

void CDlgMaintain::OnBnClickedButtonExport()
{
    //Get the selected game if any, exit routine if nothing selected
    POSITION pos = m_wndList.GetFirstSelectedItemPosition();
    if(pos == NULL)
        return;

    //Get the file name - we will use sdl (sudoku LIST) as the extension
    CFileDialog dlg(FALSE        //save as
                    , _T("sdl")    //default extension
                    , NULL        //no file name 
                    , OFN_OVERWRITEPROMPT    //if file exist warn about replacing it
                    , _T("Sudoku Lists (*.sdl)|*.sdl||")    //Filter for the file extensions - allows only sdl file extensions
                    , this); //Parent window


    if(dlg.DoModal() != IDOK)    //display and exit function if cancel was used
        return;

    //open a stdio file for writing the exported items
    CStdioFile file(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite);

    int iIndex;
    while(pos != NULL) //Is something selected in the list
    {
        iIndex = m_wndList.GetNextSelectedItem(pos);
        file.WriteString(m_wndList.GetItemText(iIndex, 1));    //Write the 'game' into the file
        
        //Append a carriage return new line pair IF there is another game to export after this one
        if(pos != NULL)
            file.WriteString(_T("\r\n"));
    }
    file.Close();
}

Note the filter - "Sudoku Lists (*.sdl)|*.sdl||". This is a string where one can define which extensions will appear in the ‘file type’ combo on the common dialog.  Specifically take note of the | character.  A single one acts as a delimiter for the different extension types to be displayed in  the combo, a double one instructs the common dialog that it is the end of the listing.  (Again a common fault resulting in questions like why doesn’t the following code work correctly? ).  The code is again fairly simple to understand.

注意过滤器-

Now to import a file in and store into the database.  We need to prompt for the file (CFileDialog again), open and parse the file, storing the individual games.  Ahhh, but what if a game is already in the database?  In the document we already encountered a similar thing of directly saving a game after entering by the keyboard – a try…catch block

现在将文件导入并存储到数据库中。 我们需要提示输入文件(再次为CFileDialog),打开并解析该文件,存储各个游戏。 嗯,但是如果数据库中已有游戏,该怎么办? 在文档中,我们已经遇到过类似的事情,即通过键盘输入后直接保存游戏– try…catch块

void CDlgMaintain::OnBnClickedButtonImport()
{
	//Get the file name - we will use sdl (sudoku LIST) as the extension
	CFileDialog dlg(TRUE		//open
					, _T("sdl")	//default extension
					, NULL		//no file name 
					, NULL		//no restrictions
					, _T("Sudoku Lists (*.sdl)|*.sdl||")	//Filter for the file extensions - allows only sdl file extensions
					, this); //Parent window


	if(dlg.DoModal() != IDOK)	//display and exit function if cancel was used
		return;

	//open a stdio file for reading the games to import
	CStdioFile file(dlg.GetPathName(), CFile::modeRead);
	CString s, szSQL;
	while(file.ReadString(s))	//keep reading from the file, return true if a line was read
	{
		szSQL.Format(_T("INSERT INTO Games (GameDetail) SELECT '%s' AS gd"), s.Left(81));	//Game is 81 chars long - removes any new line coding

		try
		{
			m_pDB->Execute(szSQL);
		}
		catch(CDaoException* pe)
		{
			if(pe->m_pErrorInfo->m_lErrorCode != 3022)	//duplicate values into a field defined as unique
			{
				AfxMessageBox(pe->m_pErrorInfo->m_strDescription, MB_ICONEXCLAMATION);
			}
			pe->Delete();
		}
	}
	file.Close();

	//Refill the list and redisplay
	AddData();
	Invalidate();

	AfxMessageBox(_T("Import completed"));
}

OK, the Import completed should be a resource string, maybe it would be nice to keep count of how many entries were added successfully and how many failed – let's do that just for completeness.

好了,“导入完成”应该是一个资源字符串,也许可以对成功添加了多少项以及失败添加了多少个计数进行计数-为了完整起见,让我们这样做。

Here is the code

这是代码

    int iAdded = 0, iFailed = 0;
    while(file.ReadString(s))    //keep reading from the file, return true if a line was read
    {
        szSQL.Format(_T("INSERT INTO Games (GameDetail) SELECT '%s' AS gd"), s.Left(81));    //Game is 81 chars long - removes any new line coding

        try
        {
            m_pDB->Execute(szSQL);
            iAdded++;
        }
        catch(CDaoException* pe)
        {
            iFailed++;
            if(pe->m_pErrorInfo->m_lErrorCode != 3022)    //duplicate values into a field defined as unique
            {
                AfxMessageBox(pe->m_pErrorInfo->m_strDescription, MB_ICONEXCLAMATION);
            }
            pe->Delete();
        }
    }
    file.Close();

    //Refill the list and redisplay
    AddData();
    Invalidate();

    s.Format(IDS_IMPORT_RESULTS, iAdded, iFailed);
    AfxMessageBox(s);

And here is how IDS_IMPORT_RESULTS is defined in the string resources: Import completed, %d games added, %d games were already in the database.  Note that the Format function of the CString can also work with a resource string, you don’t have to hard code it in the implementation.

以下是在字符串资源中定义IDS_IMPORT_RESULTS的方式:

结论: (Conclusion:)

We have seen a modal dialog in use.

我们已经看到了使用中的模式对话框。

We have customised dialog constructors to pass information (instead of via member functions after construction).

我们定制了对话框构造函数以传递信息(而不是在构造后通过成员函数传递)。

We have seen how to use a (report style) list control.

我们已经看到了如何使用(报告样式)列表控件。

We have implemented data manipulation in a database via SQL commands.

我们已经通过SQL命令在数据库中实现了数据操作。

We have used a common file dialog (CFileDialog).

我们使用了通用文件对话框(CFileDialog)。

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

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

该系列中的上一篇文章在这里: MFC中的Sudoku:第9部分

There we implemented a simple undo mechanism.

在那里,我们实现了一个简单的撤消机制。

Next article in the series is here:  Sudoku in MFC: Part 11

该系列的下一篇文章在这里: MFC中的Sudoku:第11部分

Here we will be working with a modeless dialog and a worker thread.  We will also share data between threads and work with a recursive function for solving a game of sudoku.

在这里,我们将使用无模式对话框和工作线程。 我们还将在线程之间共享数据,并使用递归函数来解决数独游戏。

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/3830/Sudoku-a-complete-MFC-application-Part-10.html

sudoku me

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值