zoukankan      html  css  js  c++  java
  • QSqlDatabase的进一步封装(多线程支持+更加简单的操作)——同时支持MySQL, SQL Server和Sqlite

    开发背景:

    1.直接用QSqlDatabase我觉得太麻烦了;

    2.对于某些数据库,多个线程同时使用一个QSqlDatabase的时候会崩溃;

    3.这段时间没什么干货放出来觉得浑身不舒服,就想写一个。

    于是,我就封装了一下

    只要简单的实例化,然后通过query()就可以拿到QSqlQuery的实例化对象。

    还自带计时,一段时间不用可自动关闭,既保证效率也不占用无用资源。

    注:需要C++11的支持

    不多说,上代码:

    JasonQt_Database.h

    [cpp] view plaincopy
     
    1. #ifndef __JasonQt_Database_h__  
    2. #define __JasonQt_Database_h__  
    3.   
    4. // C++ lib import  
    5. #include <functional>  
    6.   
    7. // Qt lib import  
    8. #include <QtCore>  
    9. #include <QtSql>  
    10.   
    11. #define PropertyDeclare(Type, Name, setName, ...)                     
    12.     private:                                                          
    13.     Type m_ ## Name __VA_ARGS__;                                      
    14.     public:                                                           
    15.     inline const Type &Name(void) const { return m_ ## Name; }        
    16.     inline void setName(const Type &Name) { m_ ## Name = Name; }      
    17.     private:  
    18.   
    19. namespace JasonQt_Database  
    20. {  
    21.   
    22. enum DatabaseModeEnum{ DatabaseNameMode, DatabaseHostMode };  
    23.   
    24. enum QueryMode { QueryAutoMode, QueryMultiMode, QuerySingleMode };  
    25.   
    26. class DatabaseSettings  
    27. {  
    28. private:  
    29.     PropertyDeclare(DatabaseModeEnum, databaseMode, setDatabaseMode)  
    30.   
    31.     PropertyDeclare(QString, databaseType, setDatabaseType)  
    32.     PropertyDeclare(QString, connectionName, setConnectionName)  
    33.   
    34.     // File mode  
    35.     PropertyDeclare(QString, nameModeName, setNameModeName)  
    36.   
    37.     // Host mode  
    38.     PropertyDeclare(QString, hostModeHostName, setHostModeHostName)  
    39.     PropertyDeclare(QString, hostModeDatabaseName, setHostModeDatabaseName)  
    40.     PropertyDeclare(QString, hostModeUserName, setHostModeUserName)  
    41.     PropertyDeclare(QString, hostModePassword, setHostModePassword)  
    42.   
    43. private:  
    44.     DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName);  
    45.   
    46. public:  
    47.     DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName);  
    48.   
    49.     DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword);  
    50. };  
    51.   
    52. class ConnectSettings  
    53. {  
    54. private:  
    55.     PropertyDeclare(int, maxOpenTime, setMaxOpenTime)  
    56.     PropertyDeclare(QueryMode, queryMode, setQueryMode)  
    57.     PropertyDeclare(int, minWaitTime, setMinWaitTime)  
    58.   
    59. public:  
    60.     ConnectSettings(const int &maxOpenTime = 60 * 1000, const QueryMode &queryMode = QueryAutoMode, const int &minWaitTime = -1);  
    61. };  
    62.   
    63. class Query  
    64. {  
    65. private:  
    66.     QSqlQuery *m_query;  
    67.     QMutex *m_mutex = NULL;  
    68.   
    69. public:  
    70.     Query(QSqlDatabase &dataBase, QMutex *mutex = NULL);  
    71.   
    72.     Query(Query &&other);  
    73.   
    74.     ~Query(void);  
    75.   
    76.     inline QSqlQuery *operator->(void) { return m_query; }  
    77.   
    78.     inline QSqlQuery *operator*(void) { return m_query; }  
    79.   
    80.     QSqlQuery *takeQuery(void);  
    81.   
    82.     QMutex *takeMutex(void);  
    83. };  
    84.   
    85. class ConnectNode: public QObject  
    86. {  
    87.      Q_OBJECT  
    88.   
    89. private:  
    90.     QSqlDatabase *m_database = NULL;  
    91.     QString m_connectionName;  
    92.   
    93.     DatabaseSettings m_dataBaseSettings;  
    94.     ConnectSettings m_connectSettings;  
    95.   
    96.     QTimer *m_autoClose = NULL;  
    97.     QMutex *m_mutex = NULL;  
    98.   
    99. public:  
    100.     ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings);  
    101.   
    102.     ~ConnectNode(void);  
    103.   
    104. public:  
    105.     Query query(void);  
    106.   
    107. public slots:  
    108.     bool addDataBase(void);  
    109.   
    110.     void removeDataBase(void);  
    111.   
    112.     bool open(void);  
    113.   
    114.     void close(void);  
    115.   
    116. signals:  
    117.     void controlStartAutoClose(void);  
    118.   
    119.     void controlStopAutoClose(void);  
    120. };  
    121.   
    122. class Control  
    123. {  
    124. private:  
    125.     DatabaseSettings m_databaseSettings;  
    126.     ConnectSettings m_connectSettings;  
    127.   
    128.     QMap<qint64, ConnectNode *> m_node;  
    129.   
    130.     QMutex m_mutex;  
    131.     QTime *m_wait = NULL;  
    132.   
    133. public:  
    134.     Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings = ConnectSettings());  
    135.   
    136.     Control(const Control &) = delete;  
    137.   
    138.     ~Control(void);  
    139.   
    140. public:  
    141.     void removeAll(void);  
    142.   
    143.     Query query(void);  
    144.   
    145. private:  
    146.     void insert(const qint64 &key);  
    147. };  
    148.   
    149. }  
    150.   
    151. #endif//__JasonQt_Database_h__  

    JasonQt_Database.cpp

    [cpp] view plaincopy
     
    1. #include "JasonQt_Database.h"  
    2.   
    3. using namespace JasonQt_Database;  
    4.   
    5. // DatabaseSettings  
    6. DatabaseSettings::DatabaseSettings(const DatabaseModeEnum &databastMode, const QString &databaseType, const QString &connectionName)  
    7. {  
    8.     m_databaseMode = databastMode;  
    9.     m_databaseType = databaseType;  
    10.     m_connectionName = connectionName;  
    11. }  
    12.   
    13. DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &nameModeName):  
    14.     DatabaseSettings(DatabaseNameMode, databaseType, connectionName)  
    15. {  
    16.     m_nameModeName = nameModeName;  
    17. }  
    18.   
    19. DatabaseSettings::DatabaseSettings(const QString &databaseType, const QString &connectionName, const QString &hostModeHostName, const QString &hostModeDatabaseName, const QString &hostModeUserName, const QString &hostModePassword):  
    20.     DatabaseSettings(DatabaseHostMode, databaseType, connectionName)  
    21. {  
    22.     m_hostModeHostName = hostModeHostName;  
    23.     m_hostModeDatabaseName = hostModeDatabaseName;  
    24.     m_hostModeUserName = hostModeUserName;  
    25.     m_hostModePassword = hostModePassword;  
    26. }  
    27.   
    28. // ConnectSettings  
    29. ConnectSettings::ConnectSettings(const int &maxOpenTime, const QueryMode &queryMode, const int &minWaitTime)  
    30. {  
    31.     m_maxOpenTime = maxOpenTime;  
    32.     m_queryMode = queryMode;  
    33.     m_minWaitTime = minWaitTime;  
    34. }  
    35.   
    36. // Query  
    37. Query::Query(QSqlDatabase &dataBase, QMutex *mutex):  
    38.     m_query(new QSqlQuery(dataBase))  
    39. {  
    40.     m_mutex = mutex;  
    41. }  
    42.   
    43. Query::Query(Query &&other)  
    44. {  
    45.     m_query = other.takeQuery();  
    46.     m_mutex = other.takeMutex();  
    47. }  
    48.   
    49. Query::~Query(void)  
    50. {  
    51.     if(m_query)  
    52.     {  
    53.         delete m_query;  
    54.     }  
    55.     if(m_mutex)  
    56.     {  
    57.         m_mutex->unlock();  
    58.     }  
    59. }  
    60.   
    61. QSqlQuery *Query::takeQuery(void)  
    62. {  
    63.     auto buf = m_query;  
    64.     m_query = NULL;  
    65.     return buf;  
    66. }  
    67.   
    68. QMutex *Query::takeMutex(void)  
    69. {  
    70.     auto buf = m_mutex;  
    71.     m_mutex = NULL;  
    72.     return buf;  
    73. }  
    74.   
    75. // ConnectNode  
    76. ConnectNode::ConnectNode(const DatabaseSettings &dataBaseSettings, const ConnectSettings &connectSettings):  
    77.     m_dataBaseSettings(dataBaseSettings),  
    78.     m_connectSettings(connectSettings)  
    79. {  
    80.     m_connectionName = QString("%1(%2)").arg(m_dataBaseSettings.connectionName()).arg(QString::number(qint64(QThread::currentThread()), 16));  
    81.   
    82.     m_mutex = new QMutex(QMutex::Recursive);  
    83.   
    84.     if(m_connectSettings.maxOpenTime())  
    85.     {  
    86.         m_autoClose = new QTimer;  
    87.         m_autoClose->setSingleShot(true);  
    88.         m_autoClose->setInterval(m_connectSettings.maxOpenTime());  
    89.         m_autoClose->moveToThread(qApp->thread());  
    90.         m_autoClose->setParent(qApp);  
    91.   
    92.         connect(m_autoClose, SIGNAL(timeout()), this, SLOT(close()), Qt::DirectConnection);  
    93.         connect(this, SIGNAL(controlStartAutoClose()), m_autoClose, SLOT(start()));  
    94.         connect(this, SIGNAL(controlStopAutoClose()), m_autoClose, SLOT(stop()));  
    95.     }  
    96.   
    97.     this->addDataBase();  
    98. }  
    99.   
    100. ConnectNode::~ConnectNode(void)  
    101. {  
    102.     if(m_mutex){ m_mutex->lock(); }  
    103.   
    104.     if(m_autoClose)  
    105.     {  
    106.         m_autoClose->deleteLater();  
    107.     }  
    108.   
    109.     this->removeDataBase();  
    110.   
    111.     if(m_mutex){ m_mutex->unlock(); }  
    112.     if(m_mutex){ delete m_mutex; }  
    113. }  
    114.   
    115. Query ConnectNode::query(void)  
    116. {  
    117.     if(!m_database)  
    118.     {  
    119.         this->addDataBase();  
    120.     }  
    121.   
    122.     if(!m_database->isOpen())  
    123.     {  
    124.         m_database->open();  
    125.     }  
    126.   
    127.     if(m_mutex){ m_mutex->lock(); }  
    128.   
    129.     Query buf(*m_database, m_mutex);  
    130.     emit controlStartAutoClose();  
    131.     return buf;  
    132. }  
    133.   
    134. bool ConnectNode::addDataBase(void)  
    135. {  
    136.     if(m_mutex){ m_mutex->lock(); }  
    137.   
    138.     if(m_database)  
    139.     {  
    140.         this->removeDataBase();  
    141.     }  
    142.   
    143.     m_database = new QSqlDatabase(QSqlDatabase::addDatabase(m_dataBaseSettings.databaseType(), m_connectionName));  
    144.   
    145.     switch(m_dataBaseSettings.databaseMode())  
    146.     {  
    147.         case DatabaseNameMode:  
    148.         {  
    149.             m_database->setDatabaseName(m_dataBaseSettings.nameModeName());  
    150.             break;  
    151.         }  
    152.         case DatabaseHostMode:  
    153.         {  
    154.             m_database->setHostName(m_dataBaseSettings.hostModeHostName());  
    155.             m_database->setDatabaseName(m_dataBaseSettings.hostModeDatabaseName());  
    156.             m_database->setUserName(m_dataBaseSettings.hostModeUserName());  
    157.             m_database->setPassword(m_dataBaseSettings.hostModePassword());  
    158.             break;  
    159.         }  
    160.         default:  
    161.         {  
    162.             if(m_mutex){ m_mutex->unlock(); }  
    163.             return false;  
    164.         }  
    165.     }  
    166.   
    167.     const auto &&flag = this->open();  
    168.   
    169.     if(m_mutex){ m_mutex->unlock(); }  
    170.   
    171.     return flag;  
    172. }  
    173.   
    174. void ConnectNode::removeDataBase(void)  
    175. {  
    176.     if(m_mutex){ m_mutex->lock(); }  
    177.   
    178.     delete m_database;  
    179.     m_database = NULL;  
    180.     QSqlDatabase::removeDatabase(m_connectionName);  
    181.   
    182.     if(m_mutex){ m_mutex->unlock(); }  
    183. }  
    184.   
    185. bool ConnectNode::open(void)  
    186. {  
    187.     if(!m_database)  
    188.     {  
    189.         this->addDataBase();  
    190.     }  
    191.   
    192.     if(m_mutex){ m_mutex->lock(); }  
    193.   
    194.     emit controlStartAutoClose();  
    195.     const auto &&Flag = m_database->open();  
    196.   
    197.     if(m_mutex){ m_mutex->unlock(); }  
    198.   
    199.     return Flag;  
    200. }  
    201.   
    202. void ConnectNode::close(void)  
    203. {  
    204.     if(m_mutex)  
    205.     {  
    206.         if(m_mutex->tryLock())  
    207.         {  
    208.             m_mutex->unlock();  
    209.             emit controlStopAutoClose();  
    210.             m_database->close();  
    211.         }  
    212.         else  
    213.         {  
    214.             emit controlStartAutoClose();  
    215.         }  
    216.     }  
    217.     else  
    218.     {  
    219.         emit controlStopAutoClose();  
    220.         m_database->close();  
    221.     }  
    222. }  
    223.   
    224. // Control  
    225. Control::Control(const DatabaseSettings &databaseSettings, const ConnectSettings &connectSettings):  
    226.     m_databaseSettings(databaseSettings),  
    227.     m_connectSettings(connectSettings)  
    228. {  
    229.     if(m_connectSettings.queryMode() == QueryAutoMode)  
    230.     {  
    231.         if(databaseSettings.databaseType() == "QMYSQL")  
    232.         {  
    233.             m_connectSettings.setQueryMode(QueryMultiMode);  
    234.         }  
    235.         else if(databaseSettings.databaseType() == "QODBC")  
    236.         {  
    237.             m_connectSettings.setQueryMode(QueryMultiMode);  
    238.         }  
    239.         else  
    240.         {  
    241.             m_connectSettings.setQueryMode(QuerySingleMode);  
    242.         }  
    243.     }  
    244.     if(m_connectSettings.queryMode() == QuerySingleMode)  
    245.     {  
    246.         this->insert(qint64(QThread::currentThread()));  
    247.     }  
    248.   
    249.     if(m_connectSettings.minWaitTime() == -1)  
    250.     {  
    251.         if(databaseSettings.databaseType() == "QMYSQL")  
    252.         {  
    253.             m_connectSettings.setMinWaitTime(0);  
    254.         }  
    255.         else if(databaseSettings.databaseType() == "QODBC")  
    256.         {  
    257.             m_connectSettings.setMinWaitTime(0);  
    258.         }  
    259.         else  
    260.         {  
    261.             m_connectSettings.setMinWaitTime(5);  
    262.             m_wait = new QTime;  
    263.             m_wait->start();  
    264.         }  
    265.     }  
    266.     else  
    267.     {  
    268.         m_wait = new QTime;  
    269.         m_wait->start();  
    270.     }  
    271. }  
    272.   
    273. Control::~Control(void)  
    274. {  
    275.     for(auto &now: m_node)  
    276.     {  
    277.         now->deleteLater();  
    278.     }  
    279.     if(m_wait)  
    280.     {  
    281.         delete m_wait;  
    282.     }  
    283. }  
    284.   
    285. void Control::removeAll(void)  
    286. {  
    287.     m_mutex.lock();  
    288.   
    289.     for(auto &Now: m_node)  
    290.     {  
    291.         Now->removeDataBase();  
    292.     }  
    293.   
    294.     m_mutex.unlock();  
    295. }  
    296.   
    297. Query Control::query(void)  
    298. {  
    299.     if(m_wait)  
    300.     {  
    301.         const auto &&flag = m_connectSettings.minWaitTime() - m_wait->elapsed();  
    302.         m_wait->restart();  
    303.         if(flag > 0)  
    304.         {  
    305.             QThread::msleep(flag);  
    306.         }  
    307.     }  
    308.   
    309.     if(m_connectSettings.queryMode() == QueryMultiMode)  
    310.     {  
    311.         m_mutex.lock();  
    312.   
    313.         const auto &¤tThread = qint64(QThread::currentThread());  
    314.         const auto &&now = m_node.find(currentThread);  
    315.         if(now != m_node.end())  
    316.         {  
    317.             auto buf((*now)->query());  
    318.   
    319.             m_mutex.unlock();  
    320.             return buf;  
    321.         }  
    322.         else  
    323.         {  
    324.             this->insert(qint64(QThread::currentThread()));  
    325.   
    326.             m_mutex.unlock();  
    327.             return this->query();  
    328.         }  
    329.     }  
    330.     else  
    331.     {  
    332.         return (*m_node.begin())->query();  
    333.     }  
    334. }  
    335.   
    336. void Control::insert(const qint64 &key)  
    337. {  
    338.     m_node[key] = new ConnectNode(m_databaseSettings, m_connectSettings);  
    339. }  

    使用:

    [cpp] view plaincopy
     
    1. // Qt lib import  
    2. #include <QCoreApplication>  
    3. #include <QtConcurrent>  
    4. #include <QSqlError>  
    5.   
    6. // JasonQt lib import  
    7. #include "JasonQt/JasonQt_Database.h"  
    8.   
    9. int main(int argc, char *argv[])  
    10. {  
    11.     QCoreApplication a(argc, argv);  
    12.   
    13.     /* 
    14.      * 注:关于附加参数 
    15.      * 这是可以不写的,如果要写的话,可以参考这个: 
    16.      * 
    17.      * 单次打开数据库最大时间:也就是最大open的时间,对于某些数据库,长时间open但不使用,不仅会造成资源浪费还会意外断开。在设置了60 * 1000后,且60秒内未进行查询,就自动断开。 
    18.      * 多线程支持:简单的说就是高级点的数据库,比如 MySql 写 JasonQt_Database::QueryMultiMode ;低级的,比如 Sqlite ,写 JasonQt_Database::QuerySingleMode ,就可以了。 
    19.      * 最小等待时间:对于某些数据库,比如Sqlite,密集查询时可能出错,此时可以适当的提升两次查询之间的最小等待时间,比如10ms 
    20.      */  
    21.   
    22.     // Sqlite的连接方式                    类型        连接名        Sqlite文件路径      单次打开数据库最大时间                多线程支持           最小等待时间  
    23.     JasonQt_Database::Control control({ "QSQLITE", "TestDB", "/Users/Jason/test.db" }, { 60 * 1000,        JasonQt_Database::QuerySingleMode, 10});  
    24.   
    25.     // MySql的连接方式                      类型      连接名        IP        数据库    用户名        密码  
    26. //  JasonQt_Database::Control control({ "QMYSQL", "TestDB", "localhost", "JasonDB", "root", "YourPassword" });  
    27.   
    28.   
    29.     // SqlServer的连接方式                  类型      连接名                                    数据库              数据库名   用户名         密码  
    30. //  JasonQt_Database::Control control({ "QODBC", "TestDB", "Driver={SQL SERVER};server=iZ23kn6vmZ\TEST;database=test;uid=sa;pwd=YourPassword;" });  
    31.   
    32.     auto insert = [&]()  
    33.     {  
    34.         auto query(control.query()); // 这里的query在解引用( -> 或者 * )后返回的是 QSqlQuery ,直接用就可以了,不需要单独打开数据库或者其他的初始化  
    35.   
    36.         query->prepare("insert into Test1 values(?)"); // 模拟插入操作  
    37.   
    38.         query->addBindValue(rand() % 1280);  
    39.   
    40.         if(!query->exec())  
    41.         {  
    42.             qDebug() << "Error" << __LINE__;  
    43.         }  
    44.   
    45.         query->clear();  
    46.   
    47.         query->prepare("insert into Test2 values(NULL, ?, ?)");  
    48.   
    49.         query->addBindValue(rand() % 1280);  
    50.         QString buf;  
    51.         for(int now = 0; now < 50; now++)  
    52.         {  
    53.             buf.append('a' + (rand() % 26));  
    54.         }  
    55.         query->addBindValue(buf);  
    56.   
    57.         if(!query->exec())  
    58.         {  
    59.             qDebug() << "Error" << __LINE__;  
    60.         }  
    61.     };  
    62.     auto delete_ = [&]()  
    63.     {  
    64.         auto query(control.query());  
    65.   
    66.         query->prepare("delete from Test1 where data = ?");  
    67.   
    68.         query->addBindValue(rand() % 1280);  
    69.   
    70.         if(!query->exec())  
    71.         {  
    72.             qDebug() << "Error" << __LINE__;  
    73.         }  
    74.   
    75.         query->clear();  
    76.   
    77.         query->prepare("delete from Test2 where data1 = ?");  
    78.   
    79.         query->addBindValue(rand() % 1280);  
    80.   
    81.         if(!query->exec())  
    82.         {  
    83.             qDebug() << "Error" << __LINE__;  
    84.         }  
    85.     };  
    86.     auto update = [&]()  
    87.     {  
    88.         auto query(control.query());  
    89.   
    90.         query->prepare("update Test1 set data = ? where data = ?");  
    91.   
    92.         query->addBindValue(rand() % 1280);  
    93.         query->addBindValue(rand() % 1280);  
    94.   
    95.         if(!query->exec())  
    96.         {  
    97.             qDebug() << "Error" << __LINE__;  
    98.         }  
    99.   
    100.         query->clear();  
    101.   
    102.         query->prepare("update Test2 set data1 = ?, data2 = ? where data1 = ?");  
    103.   
    104.         query->addBindValue(rand() % 1280 + 1);  
    105.         QString buf;  
    106.         for(int now = 0; now < 50; now++)  
    107.         {  
    108.             buf.append('a' + (rand() % 26));  
    109.         }  
    110.         query->addBindValue(buf);  
    111.         query->addBindValue(rand() % 1280 + 1);  
    112.   
    113.         if(!query->exec())  
    114.         {  
    115.             qDebug() << "Error" << __LINE__;  
    116.         }  
    117.     };  
    118.     auto select = [&]()  
    119.     {  
    120.         auto query(control.query());  
    121.   
    122.         query->prepare("select * from Test1 where data = ?");  
    123.   
    124.         query->addBindValue(rand() % 1280);  
    125.   
    126.         if(!query->exec())  
    127.         {  
    128.             qDebug() << "Error" << __LINE__;  
    129.         }  
    130.   
    131.         query->clear();  
    132.   
    133.         query->prepare("select * from Test2 where data1 = ?");  
    134.   
    135.         query->addBindValue(rand() % 1280);  
    136.   
    137.         if(!query->exec())  
    138.         {  
    139.             qDebug() << "Error" << __LINE__;  
    140.         }  
    141.     };  
    142.   
    143.     volatile int count = 0, last = 0;  
    144.   
    145.     QTime time;  
    146.     time.start();  
    147.   
    148.     QThreadPool::globalInstance()->setMaxThreadCount(10);  
    149.   
    150.     for(int now = 0; now < 3; now++) // 开启3个线程测试  
    151.     {  
    152.         QtConcurrent::run([&]()  
    153.         {  
    154.             while(true)  
    155.             {  
    156.                 count++;  
    157.                 if(!(count % 1000))  
    158.                 {  
    159.                     const auto &&now = time.elapsed();  
    160.                     qDebug() << now - last; // 打印每完成1000语句的时间  
    161.                     last = now;  
    162.                 }  
    163.   
    164.                 switch(rand() % 20)  
    165.                 {  
    166.                     case 0:  { insert(); break; }  
    167.                     case 1:  { delete_(); break; }  
    168.                     case 2:  { update(); break; }  
    169.                     default: { select(); break; }  
    170.                 }  
    171.             }  
    172.             QThread::msleep(10);  
    173.         });  
    174.     }  
    175.   
    176.     return a.exec();  
    177. }  

    我也写了一个示例工程,可以前往这里下载

    http://download.csdn.net/detail/wsj18808050/8566497

    http://blog.csdn.net/wsj18808050/article/details/44891715

  • 相关阅读:
    ASP.NET WEB开发,实现上传图片
    使用Word API打开Word文档 ASP.NET编程中常用到的27个函数集
    工资单的编辑与保存
    生成工资表
    系统设置
    空间参考
    Envelope几何对象 Curve对象几何对象 Multipatch几何对象 Geometry集合接口 IGeometryCollection接口
    Polygone对象
    Segment,Path,Ring和Polyline对象
    [Android]使用ActivityGroup来切换Activity和Layout
  • 原文地址:https://www.cnblogs.com/findumars/p/5034637.html
Copyright © 2011-2022 走看看