基于托管的C++在一定程度上去除了Native C++语法的复杂性,并且提供了灵活多变的代码组织方式,以下就以一个进行数据库CRUD的小程序来管窥一下C++,我写的这个小程序其实是利用了C#WinForm编程的功力。
先看下软件运行截图吧:
(图1,当加载大数据量的数据时,界面会采用异步方式加载,并给用户提示,涉及到异步调用)
(图2,数据加载完成,会给用户提示)
(图3,更新操作的界面,更新完毕会自动刷新主窗体,涉及到委托回调)
首先来说明连接数据库问题,这里以Sybase为例:
对于Sybase来说,.net没有提供专门的类来操作,所以需要用到ODBC来操作。具体的操作步骤就是在系统DSN中创建一个数据库连接串,然后通过如下的代码来进行操作:
System::String^ connStr = "Driver={Sybase ODBC Driver ASE 12.0};Srvr=MYTestDB;database=TESTDB;uid=sa;pwd=*****;";
然后利用System::Data::Odbc命名空间下的一些方法类来进行操作。
这里我们先来创建数据库,并循环向其中添加1W条数据记录:
create table myTemp ( userId int identity, userName varchar(30), userPass varchar(30), userAddress varchar(500), userSex bit, userPhone varchar(13), regDate datetime, ) lock allpages with identity_gap = 1 on 'default' set IDENTITY_INSERT myTemp off set IDENTITY_UPDATE myTemp off insert into myTemp ( userId,userName,userPass,userAddress,userSex,userPhone,regDate) values(1,'scy','2511','china, shanghai',1,'13101993996',getdate()) exec createDefaultObjectPermissions @objectName='myTemp' declare @count int select @count = 10000 begin while @count > 0 insert into myTemp ( userName,userPass,userAddress,userSex,userPhone,regDate) values('scy','2511','china, shanghai',1,'13101993996',getdate()) select @count = @count - 1 end
先看查询数据库的代码:
System::String^ querySQL = "select * from myTemp"; System::String^ connStr = "Driver={Sybase ODBC Driver ASE 12.0};Srvr=MYTestDB;database=TESTDB;uid=sa;pwd=*****;";
System::Data::Odbc::OdbcConnection^ conn = gcnew System::Data::Odbc::OdbcConnection(connStr); conn->Open(); System::Data::Odbc::OdbcDataAdapter^ oda = gcnew System::Data::Odbc::OdbcDataAdapter(querySQL,conn); System::Data::DataSet^ ds =gcnew System::Data::DataSet(); try { oda->Fill(ds,"trade"); } catch(System::Exception^ ex) { MessageBox::Show(ex->Message,"Notification Error",MessageBoxButtons::OKCancel,MessageBoxIcon::Error); } conn->Close();
通过将查询的代码填充到DataSet容器中,可以很方便的用来进行离线数据的操作。
需要注意的是,如果是.net自身提供的静态类,需要用::(双冒号)来进行操作,如果想自动释放不用的对象,就在申明的时候加上^符号,加上了^符号以后,访问其方法得用->符号。
上面的这段代码,是查询数据库的,如果数据库记录非常多,那么当程序加载的时候,肯定会非常慢,可能会阻塞UI显示达数十秒之久,这个在用户体验上面是非常不友好的,如果解决这个问题呢?当然得用到异步加载功能,其实在Visual C++中实现异步加载和在C#中实现,没有什么区别:
首先,我们的耗时的代码在上面,我们把他写到了一个BindData()的方法里面。然后我们声明一个委托,以便能够对这个方式实现异步调用:
delegate void BindDelegate();
这样,我们就可以通过这个BindDelegate委托的BeginInvoke方法来实现,具体代码如下:
BindDelegate^ bindDelegate = gcnew BindDelegate(this,&CPlusApp::Form1::BindData); IAsyncResult^ result = bindDelegate->BeginInvoke(gcnew System::AsyncCallback(this,&CPlusApp::Form1::CallBack),bindDelegate); toolStripStatusLabel1->Text="Loading the Data to dataGridView now, pls wait...";
当委托在加载的时候,用户界面会出现提示“Loading the Data to dataGridView now, pls wait... “,那么当加载完毕以后,我们该怎么处理呢?我们首先应该还原委托对象,然后获取返回值,由于这里的BindData()方法返回空值,我们无需过多的业务处理,代码如下:
private: System::Void CallBack(System::IAsyncResult^ iar) { BindDelegate^ asyncResult = (BindDelegate^)iar->AsyncState; asyncResult->EndInvoke(iar); toolStripStatusLabel1->Text="Loading Complete..."; }
当然,写到这里,只能说明数据已经全部加载到了DataSet中,如何将数据集中的数据绑定到数据列表控件dataGridView1上呢?
说到这里,就出现了一个问题,如果直接绑定的话,势必需要跨越线程操作(从异步处理线程跨越到界面线程中),会出现错误提示。这里就需要我们通过判断dataGridView1控件是否需要跨线程,来通过委托方式进行,代码如下:
private:System::Void BindGridViewCrossThreads(DataSet^ ds) { if(dataGridView1->InvokeRequired) { dataGridViewCrossThreadsDelegate^ crossThreads = gcnew dataGridViewCrossThreadsDelegate(this,&CPlusApp::Form1::BindGridViewCrossThreads); dataGridView1->Invoke(crossThreads,ds); } else { dataGridView1->DataSource = ds->Tables["trade"]; } }
其中dataGridViewCrossThreadsDelegate委托定义如下,需要与函数Void BindGridViewCrossThreads(DataSet^ ds)参数数目、类型以及返回方式保持一致:
delegate void dataGridViewCrossThreadsDelegate(DataSet^ ds); // used for crossing threads
好了,做到这里,我们的界面已经能够正常的显示了,并且在加载数据和加载完成的时候,均有用户提示。
下面是添加和更新操作:
System::String^ userName = textBox1->Text; System::String^ userPass = textBox2->Text; System::String^ userAddr = textBox3->Text; System::String^ userPhone = textBox5->Text; int sex = rbtnMale->Checked?1:0; System::String^ connStr = "Driver={Sybase ODBC Driver ASE 12.0};Srvr=MYTestDB;database=TESTDB;uid=sa;pwd=*****;"; System::Data::Odbc::OdbcConnection^ conn = gcnew System::Data::Odbc::OdbcConnection(connStr); conn->Open(); System::Data::Odbc::OdbcCommand^ cmd =nullptr; if(button1->Text->Equals("Add")) { System::String^ insertSQL = "insert into myTemp (userName,userPass,userAddress,userSex,userPhone,regDate) values('"+userName+"','"+userPass+"','"+userAddr+"',"+sex+",'"+userPhone+"',getdate())"; cmd = gcnew System::Data::Odbc::OdbcCommand(insertSQL,conn); } else if(button1->Text->Equals("Update")) { if(userId == 0) return ; System::String^ insertSQL = "update myTemp set userName = '"+userName+"',userPass = '"+userPass+"',userAddress = '"+userAddr+"',userSex = "+sex+",userPhone = '"+userPhone+"' where userId = "+userId; cmd = gcnew System::Data::Odbc::OdbcCommand(insertSQL,conn); } int result = cmd->ExecuteNonQuery(); if(result > 0) { MessageBox::Show(""+button1->Text+" Successfully!","Success",System::Windows::Forms::MessageBoxButtons::OK,System::Windows::Forms::MessageBoxIcon::Information); } else { MessageBox::Show(""+button1->Text+" Fail,Please check!","Fail",System::Windows::Forms::MessageBoxButtons::OK,System::Windows::Forms::MessageBoxIcon::Error); return; } conn->Close(); TransEvent(); this->Close();
注意这里的TransEvent()实现了委托回调,目的是添加完毕后,自动刷新主窗体数据。
好了,暂时介绍到这里,希望在以后项目中能够提供一定的指导作用。
代码下载: