目录
2.设置事件响应程序、数据库连接、登录信息传递(子窗口源文件):
6. 实现Check Box与其对应的Edit Control查询条件获取,组成SQL语句
设计环境说明:
1.程序与库:VS2019,PostgreSQL14,libpqxx6.3
2.以postgres数据库进行对表student查询
3. Student 表各字段类型
Sno char(8) primary key ,
Sname char(10) not null,
Ssex char(2),
Sage integer,
Dno char(2),
Sclass char(6) ,
Address char(10);
(一)设计Dialog界面
1.在子窗口NEWDialog中:
- 用Static Text和Edit Control控件构成,其中需要设置ID的是Edit Control控件,因为后续需要用到ID来获取控件中输入的信息;
2.在主窗口PostgreDialog中:
- 用Static Text、Check Box、List Control、Edit Control控件构成,其中全部控件都需要设置ID,便于后续使用,同时显示查询结果的List Control设置为只读,设置左侧滚动条,显示查询语句的Edit Control设置为多行,Auto HScroll设置为False;
(二)程序设计思路
1.优先弹出窗口选择:
- 设计在运行程序时先弹出子窗口,在项目源文件(PostgreMFC.cpp)修改m_pMainWnd所指向的dialog;
NEWDialog dlg; m_pMainWnd = &dlg; INT_PTR nResponse = dlg.DoModal();
2.设置事件响应程序、数据库连接、登录信息传递(子窗口源文件):
- 给“OK”控件设置事件响应程序,当点击OK时进行数据库连接,连接成功则提示“Connected!”,连接不成功则返回错误原因,并再次打开登录窗口。其中在子窗口头文件中定义一个全局变量(String)存储登录信息,在源文件中声明,并在拼接完登录信息后,存储登录信息;
void NEWDialog::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
CDialogEx::OnOK();
pqxx::connection* conn = nullptr;
try
{
//获取输入框中的文本
CString host1, port1, user1, dbname1, password1;
GetDlgItemText(IDC_EDIT_HOST, host1);
GetDlgItemText(IDC_EDIT_PORT, port1);
GetDlgItemText(IDC_EDIT_USERNAME, user1);
GetDlgItemText(IDC_EDIT_DATABASE, dbname1);
GetDlgItemText(IDC_EDIT_PASSWORD, password1);
std::string host = CT2A(host1);
std::string port = CT2A(port1);
std::string user = CT2A(user1);
std::string dbname = CT2A(dbname1);
std::string password = CT2A(password1);
//拼接连接字符串
std::string connection_string = "host=" + host + " port=" + port + " user=" + user + " dbname=" + dbname + " password=" + password;
global_login = connection_string;//将登录信息传到全局
//连接数据库
conn = new pqxx::connection(connection_string);
//conn = new pqxx::connection("host=127.0.0.1 user=postgres dbname=postgres password=abc123");
// 判断连接是否成功
if (conn->is_open())
{
MessageBoxW((LPCTSTR)L"Connected!");
CPostgreMFCDlg dlg;//打开主窗口
dlg.DoModal();
// 关闭子窗口
//EndDialog(0);
}
}
catch (const std::exception& e)
{
// 输出错误信息
std::string error_message = e.what();//多字节字符,但是项目为Unicode字符集
//对错误信息进行转换,变为宽字节
CStringW wide_error_message(error_message.c_str());
MessageBoxW(NULL, wide_error_message);
NEWDialog dlg;//重新打开窗口
dlg.DoModal();
}
// 在连接完毕后释放指针
if (conn != nullptr)
delete conn;
}
- 当返回的报错信息出现如下的乱码状况,则说明存在字符转换错误,需要用CStringW wide_error_message(error_message.c_str());将UTF8类型的错误消息转为能在程序正常显示的Unicode类型;
- 给“查询”设置事件响应程序,初始时先进行数据库的连接判断,创建查询任务,接着在逐步在响应程序中加入全查询语句,查询语句显示到Edit control,对语句进行转换处理,查询结果返回到List control,对Check box查询条件进行组合,合成查询语句,
3.在主窗口进行数据库连接并进行全查询(主窗口源文件中)
- 通过打断点调试登录信息是否传到主窗口,通过查看语句是否进入到if循环内部判断,并无控制台输出;
- 创建一个查询任务进行对表的全查询,并接收返回的结果信息,同时需要对返回的结果进行分行提取,识别每一个字段对应的结果;
//pqxx::result result = txn.exec("SELECT * FROM student;"); // 获取查询结果 std::string output; // 遍历结果集 try { for (auto row : result) { // 获取每一行的字段值 std::string sno = row[0].as<std::string>(); std::string sname1 = row[1].as<std::string>(); //int sage = row[2].as<int>(); std::string ssex1 = row[2].as<std::string>(); std::string sage=row[3].as<std::string>(); std::string dno = row[4].as<std::string>(); std::string sclass = row[5].as<std::string>(); std::string address1 = row[6].as<std::string>(); //.......后面还会加 }
- 但是此时若发现sname,ssex等文字为乱码,说明编码问题又出现了,也需要对字符串类型进行转换,这里不使用第三方库,引入头文件#include <windows.h>,并在“查询”控件响应程序函数前,创建如下转换函数ConvertUTF8ToWide,类型由string转为wstring;
// 将 UTF-8 编码的字符串转换为宽字节 std::wstring ConvertUTF8ToWide(const std::string& utf8String) { int wideLength = MultiByteToWideChar(CP_UTF8, 0, utf8String.c_str(), -1, nullptr, 0); std::wstring wideString(wideLength, L'\0'); MultiByteToWideChar(CP_UTF8, 0, utf8String.c_str(), -1, &wideString[0], wideLength); return wideString; }
- 接着对字符中需要转换的字符进行转换,如下图代码,由于只有文字出现乱码,这里只对文字类型的字段分别调用转换函数,
//pqxx::result result = txn.exec("SELECT * FROM student;"); // 获取查询结果 std::string output; // 遍历结果集 try { for (auto row : result) { // 获取每一行的字段值 std::string sno = row[0].as<std::string>(); std::string sname1 = row[1].as<std::string>(); //int sage = row[2].as<int>(); std::string ssex1 = row[2].as<std::string>(); std::string sage=row[3].as<std::string>(); std::string dno = row[4].as<std::string>(); std::string sclass = row[5].as<std::string>(); std::string address1 = row[6].as<std::string>(); // 由多字节转为转换为 Unicode编码 std::wstring sname = ConvertUTF8ToWide(sname1); std::wstring ssex = ConvertUTF8ToWide(ssex1); std::wstring address = ConvertUTF8ToWide(address1); //......后面再加 }
4.将查询语句SQL显示到Edit Contrl控件
- 在生成查询语句后,用如下代码获取Edit control控件(ID为IDC_EDIT_SEARCH)的指针,然后使用 SetWindowText 函数将 sqlQuery (为查询的SQL语句,类型为CString)的值设置为编辑框控件的文本内容。这样便可以将生成的 SQL 查询语句显示在编辑框中;
// 将生成的 SQL 查询语句设置为 Edit Control 的文本 CEdit* pEditSearch = (CEdit*)GetDlgItem(IDC_EDIT_SEARCH); pEditSearch->SetWindowText(sqlQuery);
5.设置List Control表头,并显示查询结果
- 在程序的OnInitDialog()函数中,按照表的各字段设置表头
BOOL CPostgreMFCDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 //.................. // TODO: 在此添加额外的初始化代码 // //设置表头,与列大小,个性化全局选中与栅格 ListContronl_Output.SetExtendedStyle(ListContronl_Output.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); ListContronl_Output.InsertColumn(0, _T("地址"), LVCFMT_CENTER, 200); ListContronl_Output.InsertColumn(0, _T("班级"), LVCFMT_CENTER, 150); ListContronl_Output.InsertColumn(0, _T("系别"), LVCFMT_CENTER, 120); ListContronl_Output.InsertColumn(0, _T("年龄"), LVCFMT_CENTER, 120); ListContronl_Output.InsertColumn(0, _T("性别"), LVCFMT_CENTER, 120); ListContronl_Output.InsertColumn(0, _T("姓名"), LVCFMT_CENTER, 130); ListContronl_Output.InsertColumn(0, _T("学号"), LVCFMT_CENTER, 150); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE }
- 同时在对返回的结果进行每一行处理的循环中,加上如下代码,对结果逐一发送到List Control进行显示
//pqxx::result result = txn.exec("SELECT * FROM student;");
// 获取查询结果
std::string output;
// 遍历结果集
try {
for (auto row : result) {
// 获取每一行的字段值
std::string sno = row[0].as<std::string>();
std::string sname1 = row[1].as<std::string>();
//int sage = row[2].as<int>();
std::string ssex1 = row[2].as<std::string>();
std::string sage=row[3].as<std::string>();
std::string dno = row[4].as<std::string>();
std::string sclass = row[5].as<std::string>();
std::string address1 = row[6].as<std::string>();
由多字节转为转换为 Unicode编码
std::wstring sname = ConvertUTF8ToWide(sname1);
std::wstring ssex = ConvertUTF8ToWide(ssex1);
std::wstring address = ConvertUTF8ToWide(address1);
// 添加新行到 List Control
int newRow = ListContronl_Output.InsertItem(ListContronl_Output.GetItemCount(), CString(sno.c_str()));
// 设置每个单元格的值
ListContronl_Output.SetItemText(newRow, 1, CString(sname.c_str()));
ListContronl_Output.SetItemText(newRow, 2, CString(ssex.c_str()));
ListContronl_Output.SetItemText(newRow, 3, CString(sage.c_str()));
ListContronl_Output.SetItemText(newRow, 4, CString(dno.c_str()));
ListContronl_Output.SetItemText(newRow, 5, CString(sclass.c_str()));
ListContronl_Output.SetItemText(newRow, 6, CString(address.c_str()));
}
}
catch (const std::exception& e) {
// 捕获异常并输出错误信息
std::cerr <<L"Error processing row: " << e.what() << std::endl;//调试用
}
// 在控件中显示查询结果
//SetDlgItemText(IDC_EDIT_OUT, CString(output.c_str()));
// 提交事务并关闭数据库连接
txn.commit();
}
6. 实现Check Box与其对应的Edit Control查询条件获取,组成SQL语句
- 首先需要设定一个初始的CString语句,后续的条件勾选,将查询内容往初始语句上加;
- 获取勾选条件:接着判断每一个Check Box控件是否被勾选,若被勾选则将Edit Control控件中所填写的信息传入到SQL初始语句中,若Edit Control控件中未填写信息,则传入的信息为空,即查询表中勾选条件为空的对象;
// // 执行查询语句
pqxx::work txn(m_conn);
CString sqlQuery = _T("SELECT * FROM student WHERE");
// 判断是否勾选 sno
if (IsDlgButtonChecked(IDC_CHECK_SNO))
{
CString sno;
GetDlgItemText(IDC_EDIT_SNO, sno);
sqlQuery += _T(" sno = '") + sno + _T("' AND ");
}
// 判断是否勾选 sname
if (IsDlgButtonChecked(IDC_CHECK_NAME))
{
CString sname;
GetDlgItemText(IDC_EDIT_NAME, sname);
sqlQuery += _T(" sname = '") + sname + _T("' AND ");
}
// 判断是否勾选 ssex
if (IsDlgButtonChecked(IDC_CHECK_SEX))
{
CString ssex;
GetDlgItemText(IDC_EDIT_SEX, ssex);
sqlQuery += _T(" ssex = '") + ssex + _T("' AND ");
}
// 判断是否勾选 sclass
if (IsDlgButtonChecked(IDC_CHECK_CLASS))
{
CString sclass;
GetDlgItemText(IDC_EDIT_CLASS, sclass);
sqlQuery += _T(" sclass = '") + sclass + _T("' AND ");
}
// 判断是否勾选 dno
if (IsDlgButtonChecked(IDC_CHECK_DEPT))
{
CString dno;
GetDlgItemText(IDC_EDIT_DEPT, dno);
sqlQuery += _T(" dno = '") + dno + _T("' AND ");
}
// 判断是否勾选 address
if (IsDlgButtonChecked(IDC_CHECK_ADDRESS))
{
CString address;
GetDlgItemText(IDC_EDIT_ADDRESS, address);
sqlQuery += _T(" address = '") + address + _T("' AND ");
}
// 判断是否勾选 sage
if (IsDlgButtonChecked(IDC_CHECKS_AGE))
{
CString ageMin, ageMax;
GetDlgItemText(IDC_EDIT_AGE_MIN, ageMin);
GetDlgItemText(IDC_EDIT_AGE_MAX, ageMax);
sqlQuery += _T(" sage >= '") + ageMin + _T("' AND sage <= '") + ageMax + _T("' AND ");
}
// 去除最后的 " AND "和" WHERE "
sqlQuery = sqlQuery.Left(sqlQuery.GetLength() - 5)+_T(";");
3.Eg.判断是否勾选学号条件(用IsDlgButtonChecked函数),if条件判断,括号中为Check Box控件ID,用函数GetDlgItemText接收学号对应Edit Control控件信息,赋给sno,同时加给初始语句,注意的是sno与AND左右都有一个空格,并且两个字符串带有英文单引号;
(_T 是一个宏,它用于根据项目的字符集设置自动将字符串常量转换为宽字符或多字节字符。它是 MFC 提供的一种字符集兼容性宏。)
- 对SQL语句进行处理:可以看到每一个勾选条件之间用AND连接,那么无论是否存在勾选条件时,语句结尾必定会有AND,注意的是AND左右都有一个空格,同时WHERE字符串长度刚好为5,这里直接用组合成的SQL语句去除后面5个字符,再加上查询语句结尾的分号“;”去构成最后的SQL查询语句;
- Unicode -> UTF-8字符转换:当我对数字类型sno,dno等勾选查询时能正常进行,当在对文字类型ssex进行勾选查询时却报错,通过调试可以知道,读取到的查询语句是正确的,那么这个时候可以猜测应该是生成的Unicode编码的文字在传给用UTF8编码的语句查询数据库时,无法正确解译为正确的文字;这时候需要对最终的查询语句进行字符转换;
通过调用两次 WideCharToMultiByte 函数来进行转换,第一次计算转换后的 UTF-8 字符串的长度,第二次进行实际的字符串转换操作,并用上获取到的字符串长度值,之间创建一个具有指定长度的 string 对象,初始化为全零,用于存储转换后的 UTF-8 字符串;
(三)程序验证