Qt学习之路_12(简易数据管理系统)

 

  前言

  最近从大陆来到台湾,之间杂事很多,挤不出时间来更新博客…

  这次主要是通过做一个简易的数据库管理系统,来学习在Qt中对数据库,xml,界面的各种操作,进一步熟悉Qt。一般而言数据通常存在文件,数据库,xml中,本文主要是介绍了sqlite,xml这2种存储数据的方法,实现了一个家用电器产品的销售小软件。参考资料为 http://www.yafeilinux.com/ 代码基本也是作者yafei的,我只是看懂一些代码然后手动敲入加稍微修改而已。反正以学习他人的代码来进一步掌握Qt编程。

  

  实验过程

  该实验分为3个部分,商品管理,销售统计,和修改密码。

  商品管理:

  在程序运行时,已经建立了商品销售的数据库,该数据库分为3个表,品牌表,分类表,密码表。分类表中有2个产品类别,为电视和空调。品牌表中对应的电视有4个品牌,空调有2个品牌,这2者初始化时已经有了库存信息,即商品的单价,剩余商品的数量等。密码表中设置了初始化密码,初始密码为”2012”。 与此同时也建立xml来存储数据,里面有日销售清单,还设置了销售日期,时间,品牌的信息,供后面销售完毕后来进行查询和存储。

  在商品管理页,选择对应的品牌,类型,和打算卖出的数量,则软件会自动计算卖出的总额和剩余对应产品的数量。单击售出的确认按钮后,右边的页面会显示销售的记录,其中的信息包括销售时间,日期,品牌的数量,价格等等。

 

  销售统计:

  销售统计这一栏用表格和饼图来显示销售产品数量的分布情况。可以在销售统计右侧的下拉列框中选择电视和空调2个类型中的一个,然后左侧的表格会显示品牌和销售数量2个量,品牌左侧有不同颜色的小方框显示。右侧显示的是其对应的饼图,饼图旁边有小方框显示不同颜色代表不同的销售商品。在左边的表格中单击表格单元格,可以看到右边饼图对应的那一个扇形的颜色更改了。销售统计中显示表格和饼图需要重写QAbstractItemView类中的很多函数,其中不少函数没有自己去敲代码,直接赋值过来的。

 

  密码更改:

  在密码更改页中,可以修改登录密码,新密码需要2遍确认,这些操作都与普通的更改密码操作类似。修改后的密码会被保存在sqlite数据库中。

 

 

  实验说明

  界面设计时的小技巧:

  1.如果把控件放在布局文件中,那么该控件自身的尺寸和控件之间的几何量会被自动改变,因为这些都是交给了布局文件来管理。那么怎么做到将设计好了的控件放入布局文件后不改变这些几何变量呢?首先,如果需要固定控件之间的距离量,可以加入弹簧;其实,如果需要固定控件的几何尺寸,可以在Qt Designer中控件的属性栏的minimumSize和maximumSize两个栏中都设置为同样大小。这样即使控件放入布局文件中,也不会更改它们本身的几何量了。

  2. 如果需要把各控件按照自己的习惯排布好,而这时不打算使用布局文件(因为布局文件会自动改变控件的大小),这时可以采用一个Stack Widget,让其它的控件任意形状放入其中。

  3. 分割器并不单单指的中间一条分割线,而是指的包括分割线2侧的widget,即可以理解为一个矩形,它也有长和宽。

 

  数据库中的一些小知识:

  数据库的一些语法中,有些地方需要加单引号,这个不能漏。

  PRIMARY KEY 约束唯一标识数据库表中的每条记录;主键必须包含唯一的值;主键列不能包含 NULL 值;每个表都应该有一个主键;并且每个表只能有一个主键。

  QSqlQuery这个类是用来查询sql数据库的类;QSqlDatabase是用来创建数据库的类;

  事务操作是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的操作。在Qt中事务操作时以transaction()开始的,以commit()函数或者rollback()函数进行结束的。这两者的区别是commit()表示提交,即把事务中所有对数据库的更新写到数据库,事务是正常结束的。Rollback()表示回滚,即如果事务运行过程中发生了某些故障,事务不能继续进行,则系统将事务中对数据库的所有已完成的操作全部撤销,回滚到事务开始的状态。

  QSqlDatabase::database()返回前面成功连接的QSqlDatabase对象。

 

  XML使用小知识点:

  对XML文档的操作有2种方法:DOM和SAX。其中DOM是把XML文档转换成应用程序可以遍历的树形结构,这样便可以随机访问其中的节点。缺点是需要把整个XML文档读入内存,这样消耗内存会很大。

  tagName 属性返回被选元素的标签名。

  这次实验通过代码来建立一个xml文件,主要是用到了QDomDocument这个类中的很多方法,xml文件有点类似树形结构,本次实验最初始建立的xml文件内容截图如下:

  

 

 

  Qt使用一些小知识点:

  QString::number(num)可以将整型的num转换成string型。

  qreal 其实就是double型。

  往QListWidget加入文字可以使用其addItem()方法.

  arg()中的参数是QString型,则其对应的百分号需要用单引号括起来。

  Qt::DecorationRole是Qt::ItemDataRole中的一种, Qt::ItemDataRole表示每一个model中的Item都有自己的一个数据集,且有自己的特色。这些特点用来指定模型中的哪一种数据类型将被使用。而Qt::DecorationRole指的是说数据将以图表的形式来呈现。

  QAbstractItemView为一个抽象item视图类,里面有很多方法可以重写。

  QPaint是一个绘图类,可以设置画笔,画刷,字体。

  在设计用户名密码登陆时,如果用户名和密码都正确,则会调用调用父类的QDialog::accept()槽函数,该函数实现关闭当前对话框,设置对话框的运行结果为QDialog::Accepted,并发送QDialog::finished(int result)信号。

  增加StackWidget页面的方法,在StackWidget上鼠标右击,选择insert page,然后继续选择在本页之前或者之后加入页码。

  商品管理中的出售商品一栏:    

  通过将产品的类型,品牌,价格,数量,金额等信息写入xml文件,各种信息都以一个时间为标签,做为该标签下面的各子标签。

 

  实验结果:

  登录界面如下:

  

 

  商品管理功能如下:

  

 

  销售统计功能如下:

  

 

  密码修改界面如下:

  

 

 

  实验主要部分代码及注释(附录有工程code下载链接):

connection.h:

#ifndef CONNECTION_H
#define CONNECTION_H

#include <QtSql>
#include <QDebug>
#include <QtXml>

//该头文件就一个函数,即创建关联表的函数
static bool createConnection()
{
    //SqlDatabase为实现数据库连接的类
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");//创建一个数据库,添加数据库驱动
    db.setHostName("tornadomeet");
    db.setDatabaseName("data.db");//设置数据库的名字
    db.setUserName("tornadomeet1");//设置数据库的用户名
//    db.setPassword("2012");//设置数据库的密码
    if(!db.open())
        return false;

    QSqlQuery query;//新建一个查询库

    //创建一个类型表,表名为type;varchar是用来保存可变长度的字符串
    query.exec("create table type(id varchar primary key, name varchar)");
    query.exec(QString("insert into type values('00', '请选择类型')"));//表中的第一个项
    query.exec(QString("insert into type values('01', '电视')"));//加入第二项
    query.exec(QString("insert into type values('02', '空调')"));//第三项

    //创建一个品牌表,表名为brand
    query.exec("create table brand(id varchar primary key, name varchar, "
               "type varchar, price int, sum int, sell int, last int)");
    query.exec(QString("insert into brand values('01', '海信', '电视', '3699', '50', '10', '40')"));
    query.exec(QString("insert into brand values('02', '创维', '电视', '3499', '20', '5', '15')"));
    query.exec(QString("insert into brand values('03', '海尔', '电视', '4199', '80', '40', '40')"));
    query.exec(QString("insert into brand values('04', '王牌', '电视', '3999', '40', '10', '30')"));
    query.exec(QString("insert into brand values('05', '海尔', '空调', '2699', '60', '10', '50')"));
    query.exec(QString("insert into brand values('06', '格力', '空调', '2799', '70', '20', '50')"));
//    query.exec("insert into type brand('05', '海尔', '空调', '2699', '60', '10', '50')");
//    query.exec("insert into type brand('06', '格力', '空调', '2799', '70', '20', '50')");
    query.exec("create table password(pwd varchar primary key)");
    query.exec("insert into password values('2012')");

    return true;
}

static bool createXml()
{
    QFile file("data.xml");//创建一个xml文件
    if(file.exists())
        return true;
    if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))//以只写方式打开,且清零以前的信息
        return false;
    QDomDocument doc;//新建一个QDomDocument类对象,它代表一个xml文件
    QDomProcessingInstruction instruction;//添加处理指令
    instruction = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
    doc.appendChild(instruction);//xml文件版本等信息
    QDomElement root = doc.createElement(QString("目录销售清单"));
    doc.appendChild(root);//增加根目录
    QTextStream out(&file);//指定文本流
    doc.save(out, 4);//将xml文档保存到文件data.xml文件中,4表示子元素缩进字符数
    file.close();

    return true;
}

#endif // CONNECTION_H

 

datamanager.h:

#ifndef DATAMANAGER_H
#define DATAMANAGER_H

#include <QMainWindow>
#include <QDomDocument>

namespace Ui {
class DataManager;
}

class QStandardItemModel;//这个类为Qt中提供了存储定制数据的通用模型

class DataManager : public QMainWindow
{
    Q_OBJECT
    
public:
    explicit DataManager(QWidget *parent = 0);
    ~DataManager();

    enum DataTimeType{Time, Date, DateTime};//定义时间的枚举类型
    QString getDataTime(DataTimeType type);
    
private slots:
    void on_sellTypeComboBox_currentIndexChanged(const QString &arg1);

    void on_sellBrandComboBox_currentIndexChanged(const QString &arg1);

    void on_sellNumSpinBox_valueChanged(int arg1);

    void on_sellCancelButton_clicked();

    void on_sellOkButton_clicked();

    void on_typeComboBox_currentIndexChanged(const QString &arg1);

    void on_updateButton_clicked();

    void on_manageButton_clicked();

    void on_chartButton_clicked();

    void on_passwordButton_clicked();

    void on_okButton_clicked();

    void on_cancelButton_clicked();

private:
    Ui::DataManager *ui;

    QDomDocument doc;
    QStandardItemModel *chartModel;
    bool docRead();
    bool docWrite();
    void writeXml();
    void createNodes(QDomElement &data);
    void showDailyList();

    void createChartModelView();
    void showChart();
};

#endif // DATAMANAGER_H

 

datamanager.cpp:

#include "datamanager.h"
#include "ui_datamanager.h"
#include "connection.h"
#include "pieview.h"
#include <QtSql>
#include <QtXml>
#include <QtGui>

DataManager::DataManager(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::DataManager)
{
    ui->setupUi(this);
    ui->stackedWidget->setCurrentIndex(0);
    QSqlQueryModel *typeModel = new QSqlQueryModel(this);//新建一个Sql查询模块
    typeModel->setQuery(QString("select name from type"));//执行改句相当于执行查询语句,其结果可以设置在组合框中
    ui->sellTypeComboBox->setModel(typeModel);

    QSplitter *splitter = new QSplitter(ui->managePage);//增加一个分隔符
    splitter->resize(680, 400);
    splitter->move(10, 40);
    splitter->addWidget(ui->toolBox);
    splitter->addWidget(ui->dailyList);
    splitter->setStretchFactor(0, 1);//第一个widget的分割器中所占面积大小之比为1
    splitter->setStretchFactor(1, 1);

    on_sellCancelButton_clicked();//初始化各控件的状态
    showDailyList();

    ui->typeComboBox->setModel(typeModel);
    createChartModelView();

}

DataManager::~DataManager()
{
    delete ui;
}

void DataManager::on_sellTypeComboBox_currentIndexChanged(const QString &arg1)
{
    if(arg1 == "请选择类型")
        {;}
    else
    {
        ui->sellBrandComboBox->setEnabled(true);
        QSqlQueryModel *model = new QSqlQueryModel(this);
        model->setQuery(QString("select name from brand where type = '%1'").arg(arg1));//注意单引号是英文状态下的
        ui->sellBrandComboBox->setModel(model);
        ui->sellCancelButton->setEnabled(true);//取消按钮可用
    }
}

void DataManager::on_sellBrandComboBox_currentIndexChanged(const QString &arg1)
{
    ui->sellNumSpinBox->setValue(0);//将数量值设置为0
    ui->sellNumSpinBox->setEnabled(false);//数量值那一栏不可更改
    ui->sellSumLineEdit->clear();//金额数清0
    ui->sellSumLineEdit->setEnabled(false);//金额一栏不可用
    ui->sellOkButton->setEnabled(false);//确定按钮也不可用

    QSqlQuery query;
    query.exec(QString("select price from brand where name ='%1' and type ='%2'").
               arg(arg1).arg(ui->sellTypeComboBox->currentText()));
    query.next();//查询记录指向相邻的下一条记录
    ui->sellPriceLineEdit->setEnabled(true);//单价输入框
    ui->sellPriceLineEdit->setReadOnly(true);
    ui->sellPriceLineEdit->setText(query.value(0).toString());//value(0)为获得第0个属性的值,这里指的是price

    query.exec(QString("select last from brand where name ='%1' and type = '%2'").
               arg(arg1).arg(ui->sellTypeComboBox->currentText()));
    query.next();//移向第一个记录
    int num = query.value(0).toInt();//取出来
    if(0 == num)
    {
        QMessageBox::information(this, tr("提示"), tr("该商品已经销售完了!"), QMessageBox::Ok);
    }
    else
    {
        ui->sellNumSpinBox->setEnabled(true);//可用使用
        ui->sellNumSpinBox->setMaximum(num);//设置最大值为剩余数量的值
        ui->sellLastNumLabel->setText(tr("剩余数量: %1").arg(num));
        ui->sellLastNumLabel->setVisible(true);//其实默认情况下就是可见的
    }


}

void DataManager::on_sellNumSpinBox_valueChanged(int arg1)
{
    if(arg1 == 0)
    {
        ui->sellSumLineEdit->clear();//清零且不可用
        ui->sellSumLineEdit->setEnabled(false);
        ui->sellOkButton->setEnabled(false);
    }
    else
    {
        ui->sellSumLineEdit->setEnabled(true);
        ui->sellSumLineEdit->setReadOnly(true);
        //qreal其实就是一个double型
        qreal sum = arg1*(ui->sellPriceLineEdit->text().toInt());//卖出的数量*单价等于总额
        ui->sellSumLineEdit->setText(QString::number(sum));//显示总额
        ui->sellOkButton->setEnabled(true);
    }
}

void DataManager::on_sellCancelButton_clicked()
{
    ui->sellTypeComboBox->setCurrentIndex(0);

    ui->sellBrandComboBox->clear();//品牌框不可用,且清零
    ui->sellBrandComboBox->setEnabled(false);

    ui->sellPriceLineEdit->clear();
    ui->sellPriceLineEdit->setEnabled(false);//单价栏不可用

    ui->sellNumSpinBox->setValue(0);//数量栏清零且不可用
    ui->sellNumSpinBox->setEnabled(false);

    ui->sellSumLineEdit->clear();
    ui->sellSumLineEdit->setEnabled(false);//总额栏不可用

    ui->sellOkButton->setEnabled(false);
    ui->sellCancelButton->setEnabled(false);//按钮不可用
    ui->sellLastNumLabel->setVisible(false);//剩余数不可见
}

void DataManager::on_sellOkButton_clicked()
{
    //QSqlDatabase::database()返回前面成功连接的QSqlDatabase对象。
    QString type = ui->sellTypeComboBox->currentText();
    QString name = ui->sellBrandComboBox->currentText();
    int value = ui->sellNumSpinBox->value();//当前打算卖出的量
    int last = ui->sellNumSpinBox->maximum()-value;//当前还剩余的量
    QSqlQuery query;
    query.exec(QString("select sell from brand where name=%1 and type=2%").arg(name).arg(type));
    query.next();//指向结果集的第一条记录
    int sell = query.value(0).toInt()+value;//总共售出量
    QSqlDatabase::database().transaction();//事务操作开始
    bool rtn = query.exec(QString("update brand set sell=%1, last=%2 where name='%3' and type='%4'").arg(sell).
                          arg(last).arg(name).arg(type));
    if(rtn)
    {
        QSqlDatabase::database().commit();//上面查询操作成功时,则提交事务操作
        QMessageBox::information(this, tr("提示"), tr("购买成功!"), QMessageBox::Ok);
        writeXml();
        showDailyList();
        on_sellCancelButton_clicked();
  //      qDebug() << "It's OK!";
    }
    else
    {
        QSqlDatabase::database().rollback();//如果上述查询操作失败,则执行事务回滚
    }
}


QString DataManager::getDataTime(DataManager::DataTimeType type)
{
    QDateTime datetime = QDateTime::currentDateTime();
    QString date = datetime.toString("yyyy-MM-dd");
    QString time = datetime.toString("hh:mm");
    QString date_time = datetime.toString("yyyy-MM-dd dddd hh:mm");
    if(type == Date)
        return date;
    else if(type == Time)
        return time;
    else return date_time;
}


//将xml文件中的内容读取到QDomDocument类对象中去
bool DataManager::docRead()
{
    QFile file("data.xml");
    if(!file.open(QIODevice::ReadOnly))//打开文件
    {
        return false;
    }
    if(!doc.setContent(&file))//读取文件
    {
        file.close();
        return false;
    }
    file.close();
    return true;
}


//将QDomDocument类对象的内容写到xml文档中去
bool DataManager::docWrite()
{
    QFile file("data.xml");
    if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
        return false;
    QTextStream out(&file);
    doc.save(out, 4);//将doc内容存入到file中
    file.close();
    return true;
}

void DataManager::writeXml()
{
    if(docRead())
    {
        QString currentDate = getDataTime(Date);//获得当前日期
        QDomElement root = doc.documentElement();//获得当前文档根节点
        if(!root.hasChildNodes())
        {
            QDomElement date = doc.createElement(QString("日期"));//增加一个标签
            QDomAttr curDate = doc.createAttribute("date");//增加一个属性
            curDate.setValue(currentDate);//给属性赋值
            date.setAttributeNode(curDate);//给新增加的标签附上该属性
            root.appendChild(date);//往根节点上添加标签
            createNodes(date);//创建标签节点
        }
        else
        {
           QDomElement date = root.lastChild().toElement();
           //已经有了今天的节点信息
           if(date.attribute("date") == currentDate)
           {
                createNodes(date);
           }
           else
           {
                QDomElement date = doc.createElement(QString("日期"));
                QDomAttr curDate = doc.createAttribute("date");
                curDate.setValue(currentDate);
                date.setAttributeNode(curDate);
                root.appendChild(date);
                createNodes(date);
           }
        }
        docWrite();//写入到xml文件中
    }
}


//创建销售商品的信息的节点
void DataManager::createNodes(QDomElement &date)
{
    //创建标签time,并为其设置属性值
    QDomElement time = doc.createElement(QString("时间"));
    QDomAttr curTime = doc.createAttribute("time");
    curTime.setValue(getDataTime(Time));
    time.setAttributeNode(curTime);

    //将新建的time标签加载到date标签上
    date.appendChild(time);

    QDomElement type = doc.createElement(QString("类型"));
    QDomElement brand = doc.createElement(QString("品牌"));
    QDomElement price = doc.createElement(QString("单价"));
    QDomElement num = doc.createElement(QString("数量"));
    QDomElement sum = doc.createElement(QString("金额"));

    QDomText text;//用来存放文本值对象
    text = doc.createTextNode(QString("%1").arg(ui->sellTypeComboBox->currentText()));
    type.appendChild(text);//type标签中的文本内容

    //brand标签
    text = doc.createTextNode(QString("%1").arg(ui->sellBrandComboBox->currentText()));
    brand.appendChild(text);

    //price标签
    text = doc.createTextNode(QString("%1").arg(ui->sellPriceLineEdit->text()));
    price.appendChild(text);

    //num标签
    text = doc.createTextNode(QString("%1").arg(ui->sellNumSpinBox->value()));
    num.appendChild(text);

    //sum标签
    text = doc.createTextNode(QString("%1").arg(ui->sellSumLineEdit->text()));
    sum.appendChild(text);

    time.appendChild(type);//将新建的标签都贴在time标签上
    time.appendChild(brand);
    time.appendChild(price);
    time.appendChild(num);
    time.appendChild(sum);
}


void DataManager::showDailyList()
{
    ui->dailyList->clear();
    if(docRead())
    {
        QDomElement root = doc.documentElement();//获取根标签
        QString title = root.tagName();//获得根标签的名字
        QListWidgetItem *listItem = new QListWidgetItem;//建立一个listWidget
        listItem->setText(QString("......%1......").arg(title));//设置显示的标题
        listItem->setTextAlignment(Qt::AlignCenter);//标题设置为中心对齐
        ui->dailyList->addItem(listItem);//将该listItem添加到QListWidgetItem中
        if(root.hasChildNodes())
        {
           QString currentDate = getDataTime(Date);//得到当前的日期
           QDomElement dateElement = root.lastChild().toElement();//得到最后一个标签
           QString date = dateElement.attribute("date");//得到该标签的的时间属性
           if(date == currentDate)
           {
                ui->dailyList->addItem("");//这里是空格一行
                ui->dailyList->addItem(QString("日期: %1").arg(date));
                ui->dailyList->addItem("");
                QDomNodeList children = dateElement.childNodes();//获得该标签的所有子元素列表
                qDebug() << children.count();
                for(int i = 0; i < children.count(); i++)
                {
                    QDomNode node = children.at(i);//得到第i个子元素的节点
                    QString time = node.toElement().attribute("time");//需先将node转换成element
                    QDomNodeList list = node.childNodes();
                    //分别获取节点中元素的内容
                    QString type = list.at(0).toElement().text();
                    QString brand = list.at(1).toElement().text();
                    QString price = list.at(2).toElement().text();
                    QString num = list.at(3).toElement().text();
                    QString sum = list.at(4).toElement().text();
                    QString str = time + " 出售 " + brand + type
                            + " " + num + "台, " + "单价:" + price
                            + "元, 共" + sum + "";
                    QListWidgetItem *tempItem =  new QListWidgetItem;
                    tempItem->setText("*************************");
                    tempItem->setTextAlignment(Qt::AlignCenter);
                    ui->dailyList->addItem(tempItem);
                    ui->dailyList->addItem(str);//显示该卖出产品的信息
               }
           }
        }
    }
}


void DataManager::createChartModelView()
{
    chartModel = new QStandardItemModel(this);
    chartModel->setColumnCount(2);//设置为2列显示
    chartModel->setHeaderData(0, Qt::Horizontal, QString("品牌"));
    chartModel->setHeaderData(1, Qt::Horizontal, QString("销售数量"));//模型的字段名

    QSplitter *splitter = new QSplitter(ui->chartPage);//新建分隔器
    splitter->resize(680, 400);
    splitter->move(10, 40);

    QTableView *table = new QTableView;
    PieView *pieChart = new PieView;

    splitter->addWidget(table);//为分隔符2侧添加widget
    splitter->addWidget(pieChart);

    splitter->setStretchFactor(0, 1);//设置比例
    splitter->setStretchFactor(1, 2);

    table->setModel(chartModel);//设置模型??
    pieChart->setModel(chartModel);

    QItemSelectionModel *selectionModel = new QItemSelectionModel(chartModel);
    table->setSelectionModel(selectionModel);//设置共用选择模型
    pieChart->setSelectionModel(selectionModel);
}


void DataManager::showChart()
{
    QSqlQuery query;
    query.exec(QString("select name, sell from brand where type='%1'").arg(ui->typeComboBox->currentText()));
    //全部删除表中的内容
    //rowCount()为返回参数索引中的行数,QModelIndex()为一个不可用的模型索引,该模型索引通常被用来在顶级项目时被父索引使用
    //removeRows()为删除参数1开始的参数2行个行
    chartModel->removeRows(0, chartModel->rowCount(QModelIndex()), QModelIndex());

    int row = 0;
    while(query.next())
    {
        int r = qrand()%256;
        int g = qrand()%256;
        int b = qrand()%256;

        chartModel->insertRows(row, 1, QModelIndex());//增加一行
        chartModel->setData(chartModel->index(row, 0, QModelIndex()), query.value(0).toString());//第一行第一列设置查询结果值
        chartModel->setData(chartModel->index(row, 1, QModelIndex()), query.value(1).toInt());
        //Qt::DecorationRole是Qt::ItemDataRole中的一种, Qt::ItemDataRole表示每一个model中的Item都有自己的一个数据集,
        //且有自己的特色。这些特点用来指定模型中的哪一种数据类型将被使用。而Qt::DecorationRole指的是说数据将以图表的形式来呈现。
        chartModel->setData(chartModel->index(row, 0, QModelIndex()), QColor(r, g, b), Qt::DecorationRole);
//        chartModel->setData(chartModel->index(row, 1, QModelIndex()), query.value(1).toInt());
//        chartModel->setData(chartModel->index(row, 0, QModelIndex()), QColor(r, g, b), Qt::DecorationRole);
        row ++;
    }
}



void DataManager::on_typeComboBox_currentIndexChanged(const QString &arg1)
{
    if(arg1 != "请选择类型")
        showChart();
}

void DataManager::on_updateButton_clicked()
{
    if(ui->typeComboBox->currentText() != "请选择类型")
        showChart();//其实是更新品牌的随机颜色
}

void DataManager::on_manageButton_clicked()
{
    ui->stackedWidget->setCurrentIndex(0);//显示stackWidget的第一页
}

void DataManager::on_chartButton_clicked()
{
    ui->stackedWidget->setCurrentIndex(1);//显示stackWidget的第二页
}

void DataManager::on_passwordButton_clicked()
{
    ui->stackedWidget->setCurrentIndex(2);//进入密码管理页
}

void DataManager::on_okButton_clicked()
{
    QSqlQuery query;
    query.exec("select pwd from password");
    query.next();
    if(ui->oldPasswordLineEdit->text().isEmpty())
        QMessageBox::warning(this, tr("请输入密码"), tr("请输入旧密码和新密码"), QMessageBox::Ok);
    else if(ui->oldPasswordLineEdit->text() != query.value(0).toString())
    {
        QMessageBox::warning(this, "密码错误", tr("输入的旧密码错误"), QMessageBox::Ok);
        ui->oldPasswordLineEdit->setFocus();
    }
    else
    {
        if(ui->newPasswordLineEdit->text() == ui->surePasswordlineEdit->text())
        {
            QString newPassword = ui->newPasswordLineEdit->text();
            query.exec(QString("update password set pwd=%1").arg(newPassword));//更新密码
            QMessageBox::information(this, tr("修改密码"), tr("修改密码成功!"), QMessageBox::Ok);
        }
        else
            QMessageBox::warning(this, tr("修改密码失败"), tr("新密码两次输入不一致!"), QMessageBox::Ok);
    }
}

void DataManager::on_cancelButton_clicked()
{
    ui->oldPasswordLineEdit->clear();
    ui->oldPasswordLineEdit->setFocus();//把输入焦点放在旧密码处,方便重新输入
    ui->newPasswordLineEdit->clear();
    ui->surePasswordlineEdit->clear();
}

 

logindialog.h:

#ifndef LOGINDIALOG_H
#define LOGINDIALOG_H

#include <QDialog>

namespace Ui {
class LoginDialog;
}

class LoginDialog : public QDialog
{
    Q_OBJECT
    
public:
    explicit LoginDialog(QWidget *parent = 0);
    ~LoginDialog();
    
private slots:

    void on_loginButton_clicked();

    void on_quitButton_clicked();

private:
    Ui::LoginDialog *ui;
};

#endif // LOGINDIALOG_H

 

logindialog.cpp:

#include "logindialog.h"
#include "ui_logindialog.h"
#include <QMessageBox>
#include <QSqlQuery>
#include <QDebug>

LoginDialog::LoginDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::LoginDialog)
{
    ui->setupUi(this);
    ui->passwordEdit->setFocus();
    ui->loginButton->setDefault(true);

}

LoginDialog::~LoginDialog()
{
    delete ui;
}


void LoginDialog::on_loginButton_clicked()
{
    if(ui->passwordEdit->text().isEmpty())
    {
        QMessageBox::information(this, tr("请输入密码"), tr("请输入密码再登录"), QMessageBox::Ok);
        ui->passwordEdit->setFocus();
    }
    else
    {
        QSqlQuery query;
        query.exec("select pwd from password");
        query.next();
        if(query.value(0).toString() == ui->passwordEdit->text())
        {
            QDialog::accept();
        }
        else
        {
           QMessageBox::warning(this, tr("密码错误"), tr("请输入正确密码后再登录"),QMessageBox::Ok);
           ui->passwordEdit->clear();
           ui->passwordEdit->setFocus();//给一个输入焦点
        }
     }
}

void LoginDialog::on_quitButton_clicked()
{
    QDialog::reject();//隐藏对话框,发送拒绝信号
}

 

preview.h:

#ifndef PIEVIEW_H
#define PIEVIEW_H

#include <QAbstractItemView>

class PieView : public QAbstractItemView
{
    Q_OBJECT
public:
    explicit PieView(QWidget *parent = 0);
    QModelIndex indexAt(const QPoint &point) const;
    void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible);
    QRect visualRect(const QModelIndex &index) const;

signals:
    
public slots:
protected slots:
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);   
    void rowsInserted(const QModelIndex &parent, int start, int end);
    void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);

protected:
    void paintEvent(QPaintEvent *event);
    void resizeEvent(QResizeEvent * /* event */);
    bool isIndexHidden(const QModelIndex &index) const;
    QRegion itemRegion(const QModelIndex &index) const;
    int horizontalOffset() const;
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,
                                    Qt::KeyboardModifiers /*modifiers*/);
    bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event);
    void setSelection(const QRect&, QItemSelectionModel::SelectionFlags command);
    void scrollContentsBy(int dx, int dy);
    int verticalOffset() const;
    QRegion visualRegionForSelection(const QItemSelection &selection) const;

private:
    int margin, totalSize, pieSize;
    int validItems;
    double totalValue;
    QPoint origin;
    QRubberBand *rubberBand;

    QRect itemRect(const QModelIndex &item) const;
    int rows(const QModelIndex &index = QModelIndex()) const;
    void updateGeometries();
    
};

#endif // PIEVIEW_H

 

preview.cpp:

#include "pieview.h"
#include <QtGui>
#include <QtCore>

#ifndef M_PI
#define M_PI 3.1415927
#endif

PieView::PieView(QWidget *parent) :
    QAbstractItemView(parent)
{
    horizontalScrollBar()->setRange(0 ,0);//设置水平拖动条范围
    verticalScrollBar()->setRange(0, 0);//设置垂直拖动条范围

    margin = 8;
    totalSize= 230;
    pieSize = totalSize - 2*margin;
    validItems = 0;
    totalValue = 0.0;
}

//这是个槽函数,当视图数据改变时调用
void PieView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    QAbstractItemView::dataChanged(topLeft, bottomRight);//当数据被改变时改变这个槽函数

    validItems = 0;//有效的item数目
    totalValue = 0.0;//总销售量
    for (int row = 0; row < model()->rowCount(rootIndex()); ++row)
    {
        QModelIndex index = model()->index(row, 1, rootIndex());//按顺序进行索引
        double value = model()->data(index).toDouble();//对应索引的销售量
        if(value > 0.0)
        {
            totalValue += value;//即统计总销售量和有效条目
            validItems ++;
        }

    }
    viewport()->update(); //视图更新
}


//该函数是当当前所选的item处于可编辑状态时对应的编辑操作
bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
    if( index.column() == 0)
        return QAbstractItemView::edit(index, trigger, event);
    else
        return false;
}


QModelIndex PieView::indexAt(const QPoint &point) const
{
    if( validItems == 0)
        return QModelIndex();
    int wx = point.x() + horizontalScrollBar()->value();
    int wy = point.y() + verticalScrollBar()->value();

    if(wx < totalSize)
    {
        double cx = wx - totalSize/2;
        double cy = totalSize/2 - wy;

        double d = pow(pow(cx, 2) + pow(cy, 2), 0.5);//求2点之间的欧式距离
        if(d == 0 || d > pieSize/2)
            return QModelIndex();

        double angle = (180/ M_PI)* acos(cx/d);//角度
        if(cy < 0)
            angle = 360 - angle;
        double startAngle = 0;//起始角度
        for(int row = 0; row < model()->rowCount(rootIndex()); ++row)
        {
            QModelIndex index = model()->index(row, 1, rootIndex());
            double value = model()->data(index).toDouble();
            if(value > 0.0)
            {
                double sliceAngle = 360*value/totalValue;
                if(angle > startAngle && angle < (sliceAngle + startAngle))
                    return model()->index(row, 1, rootIndex());
                startAngle += sliceAngle;
            }
        }
    }
    else
    {
        double itemHeight = QFontMetrics(viewOptions().font).height();
        int listItem = int((wy - margin) / itemHeight);
        int validRow = 0;

        for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

            QModelIndex index = model()->index(row, 1, rootIndex());
            if (model()->data(index).toDouble() > 0.0) {

                if (listItem == validRow)
                    return model()->index(row, 0, rootIndex());

                validRow++;
            }
        }
    }
    return QModelIndex();
}


//判断索引是否隐藏
bool PieView::isIndexHidden(const QModelIndex &index) const
{
    return false;
}


QRect PieView::itemRect(const QModelIndex &index) const
{
    if(!index.isValid())
        return QRect();
    QModelIndex valueIndex;
    if(index.column() != 1)
        valueIndex = model()->index(index.row(), 1, rootIndex());
    else
        valueIndex = index;
    if(model()->data(valueIndex).toDouble()> 0.0)
    {
        int listItem = 0;
        for(int row = index.row()-1; row>-0; --row)
        {
            if(model()->data(model()->index(row, 1, rootIndex())).toDouble()>0.0)
                listItem++;
        }
        double itemHeight;
        switch(index.column())
        {
            case 0:
            itemHeight = QFontMetrics(viewOptions().font).height();//获取字体的高度
            return QRect(totalSize, int(margin + listItem*itemHeight),
                         totalSize - margin, int(itemHeight));
            case 1:
                return viewport()->rect();
        }
    }
    return QRect();
}

QRegion PieView::itemRegion(const QModelIndex &index) const
{
    if (!index.isValid())
        return QRegion();

    if (index.column() != 1)
        return itemRect(index);

    if (model()->data(index).toDouble() <= 0.0)
        return QRegion();

    double startAngle = 0.0;
    for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

        QModelIndex sliceIndex = model()->index(row, 1, rootIndex());
        double value = model()->data(sliceIndex).toDouble();

        if (value > 0.0) {
            double angle = 360*value/totalValue;

            if (sliceIndex == index) {
                QPainterPath slicePath;
                slicePath.moveTo(totalSize/2, totalSize/2);
                slicePath.arcTo(margin, margin, margin+pieSize, margin+pieSize,
                                startAngle, angle);
                slicePath.closeSubpath();

                return QRegion(slicePath.toFillPolygon().toPolygon());
            }

            startAngle += angle;
        }
    }

    return QRegion();
}


int PieView::horizontalOffset() const
{
    return horizontalScrollBar()->value();
}


//鼠标按下时的反应处理
void PieView::mousePressEvent(QMouseEvent *event)
{
    QAbstractItemView::mousePressEvent(event);
    origin = event->pos();
    if (!rubberBand)
        //QRubberBand为设置橡皮筋框
        rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
    rubberBand->setGeometry(QRect(origin, QSize()));
    rubberBand->show();
}


//鼠标移动时的处理
void PieView::mouseMoveEvent(QMouseEvent *event)
{
    if (rubberBand)
        rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
    QAbstractItemView::mouseMoveEvent(event);
}

//鼠标释放时的处理
void PieView::mouseReleaseEvent(QMouseEvent *event)
{
    QAbstractItemView::mouseReleaseEvent(event);
    if (rubberBand)
        rubberBand->hide();
    viewport()->update();
}

//光标移动时的处理
QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,
                                Qt::KeyboardModifiers /*modifiers*/)
{
    QModelIndex current = currentIndex();

    switch (cursorAction) {
        case MoveLeft:
        case MoveUp://向左和向上处理效果一样
            if (current.row() > 0)
                current = model()->index(current.row() - 1, current.column(),
                                         rootIndex());
            else
                current = model()->index(0, current.column(), rootIndex());
            break;
        case MoveRight:
        case MoveDown://向下和向右处理效果一样
            if (current.row() < rows(current) - 1)
                current = model()->index(current.row() + 1, current.column(),
                                         rootIndex());
            else
                current = model()->index(rows(current) - 1, current.column(),
                                         rootIndex());
            break;
        default:
            break;
    }

    viewport()->update();
    return current;
}


void PieView::paintEvent(QPaintEvent *event)
{
    QItemSelectionModel *selections = selectionModel();//selectionModel()为返回当前的选择模型
    QStyleOptionViewItem option = viewOptions();//返回一个带有填充视图的调试版,字体,对齐方式等熟悉的QStyleOptionViewItem结构
    QBrush background = option.palette.base();//获得调色版的背景色
    QPen foreground(option.palette.color(QPalette::WindowText));//定义一个用于前景色的画笔
    QPainter painter(viewport());//设置绘图类
    painter.setRenderHint(QPainter::Antialiasing);//设置渲染属性,这里设置为使用抗锯齿效果绘图
    painter.fillRect(event->rect(), background);//用背景色填充矩形
    painter.setPen(foreground);//设置画笔的前景色

    QRect pieRect = QRect(margin, margin, pieSize, pieSize);//该矩形用来画饼状图和饼状图旁边的图示的

    //开始绘制饼图
    if(validItems > 0)
    {
        painter.save();//保存绘图类
        //translate()为将坐标系移动
        //如果滑动条向右移,那么坐标系需向左移动
        painter.translate(pieRect.x()-horizontalScrollBar()->value(), pieRect.y()-verticalScrollBar()->value());
        painter.drawEllipse(0, 0, pieSize, pieSize);//在指定的矩形中画椭圆,两者的中心重合,椭圆长短轴和矩形的长宽对应起来
        double startAngle = 0.0;
        int row;
        //model()为返回当前视图的model
        for(row = 0; row < model()->rowCount(rootIndex()); ++row)
        {
            //rootIndex()为model的根索引
            QModelIndex index = model()->index(row, 1, rootIndex());//销量索引,即后面单击销售数量那一列能用到
            double value = model()->data(index).toDouble();//通过索引获取销售量
            if(value > 0)
            {
                double angle = 360*value/totalValue;
                QModelIndex colorIndex = model()->index(row, 0, rootIndex());//颜色栏索引号
                QColor color = QColor(model()->data(colorIndex, Qt::DecorationRole).toString());//获取表中的颜色
                if(currentIndex() == index)//currentIndex为当前模型的item
                    painter.setBrush(QBrush(color, Qt::Dense4Pattern));//为画图类设置画刷,其中Qt::Dense4Pattern为画笔的样式
                else if(selections->isSelected(index))
                    painter.setBrush(QBrush(color, Qt::Dense3Pattern));//所选的行用另一种样式表示
                else
                    painter.setBrush(QBrush(color));//其它的没选中的用它原来的颜色显示
                painter.drawPie(0, 0, pieSize, pieSize, int(startAngle*16), int(angle*16));//绘制饼图
                startAngle += angle;
            }
        }
        painter.restore();//保存绘画设置

        //绘制饼图旁边的图示
        int keyNumber = 0;
        for(row = 0; row < model()->rowCount(rootIndex()); ++row)
        {
            QModelIndex index = model()->index(row, 1, rootIndex());
            double value = model()->data(index).toDouble();//取出销售数量值
            if(value > 0.0)
            {
                QModelIndex labelIndex = model()->index(row, 0, rootIndex());//以第0列为索引
                QStyleOptionViewItem option = viewOptions();
                option.rect = visualRect(labelIndex);//返回虚拟的矩形,即被占住的矩形也能显示
                if(selections->isSelected(labelIndex))
                    option.state |= QStyle::State_Selected;//用于指定该widget是否被选中
                if(currentIndex() == labelIndex)
                    option.state |= QStyle::State_HasFocus;//用于指定该widget是否有焦点
                itemDelegate()->paint(&painter, option, labelIndex);//itemDelegate()类为Qt中的代理类
                keyNumber ++;
            }


        }
    }

}



//改变几何形状
void PieView::resizeEvent(QResizeEvent * /* event */)
{
    updateGeometries();
}


//返回父级index的行数
int PieView::rows(const QModelIndex &index) const
{
    return model()->rowCount(model()->parent(index));
}


//插入行
void PieView::rowsInserted(const QModelIndex &parent, int start, int end)
{
    for (int row = start; row <= end; ++row) {

        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();

        if (value > 0.0) {
            totalValue += value;
            validItems++;
        }
    }

    QAbstractItemView::rowsInserted(parent, start, end);//调用父类的方法
}


//删除行
void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    for (int row = start; row <= end; ++row) {

        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();
        if (value > 0.0) {
            totalValue -= value;
            validItems--;
        }
    }

    QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);//调用父类的方法
}

//滑动条移动
void PieView::scrollContentsBy(int dx, int dy)
{
    viewport()->scroll(dx, dy);
}


void PieView::scrollTo(const QModelIndex &index, ScrollHint)
{
    QRect area = viewport()->rect();
    QRect rect = visualRect(index);

    if (rect.left() < area.left())
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + rect.left() - area.left());
    else if (rect.right() > area.right())
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + qMin(
                rect.right() - area.right(), rect.left() - area.left()));

    if (rect.top() < area.top())
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + rect.top() - area.top());
    else if (rect.bottom() > area.bottom())
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + qMin(
                rect.bottom() - area.bottom(), rect.top() - area.top()));

    update();
}

void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{

    QRect contentsRect = rect.translated(
                            horizontalScrollBar()->value(),
                            verticalScrollBar()->value()).normalized();

    int rows = model()->rowCount(rootIndex());
    int columns = model()->columnCount(rootIndex());
    QModelIndexList indexes;

    for (int row = 0; row < rows; ++row) {
        for (int column = 0; column < columns; ++column) {
            QModelIndex index = model()->index(row, column, rootIndex());
            QRegion region = itemRegion(index);
            if (!region.intersect(contentsRect).isEmpty())
                indexes.append(index);
        }
    }

    if (indexes.size() > 0) {
        int firstRow = indexes[0].row();
        int lastRow = indexes[0].row();
        int firstColumn = indexes[0].column();
        int lastColumn = indexes[0].column();

        for (int i = 1; i < indexes.size(); ++i) {
            firstRow = qMin(firstRow, indexes[i].row());
            lastRow = qMax(lastRow, indexes[i].row());
            firstColumn = qMin(firstColumn, indexes[i].column());
            lastColumn = qMax(lastColumn, indexes[i].column());
        }

        QItemSelection selection(
            model()->index(firstRow, firstColumn, rootIndex()),
            model()->index(lastRow, lastColumn, rootIndex()));
        selectionModel()->select(selection, command);
    } else {
        QModelIndex noIndex;
        QItemSelection selection(noIndex, noIndex);
        selectionModel()->select(selection, command);
    }

    update();
}

void PieView::updateGeometries()
{
    horizontalScrollBar()->setPageStep(viewport()->width());
    horizontalScrollBar()->setRange(0, qMax(0, 2*totalSize - viewport()->width()));
    verticalScrollBar()->setPageStep(viewport()->height());
    verticalScrollBar()->setRange(0, qMax(0, totalSize - viewport()->height()));
}

int PieView::verticalOffset() const
{
    return verticalScrollBar()->value();
}


QRect PieView::visualRect(const QModelIndex &index) const
{
    QRect rect = itemRect(index);
    if (rect.isValid())
        return QRect(rect.left() - horizontalScrollBar()->value(),
                     rect.top() - verticalScrollBar()->value(),
                     rect.width(), rect.height());
    else
        return rect;
}

QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const
{
    int ranges = selection.count();

    if (ranges == 0)
        return QRect();

    QRegion region;
    for (int i = 0; i < ranges; ++i) {
        QItemSelectionRange range = selection.at(i);
        for (int row = range.top(); row <= range.bottom(); ++row) {
            for (int col = range.left(); col <= range.right(); ++col) {
                QModelIndex index = model()->index(row, col, rootIndex());
                region += visualRect(index);
            }
        }
    }
    return region;
}

 

main.cpp:

#include <QApplication>
#include "datamanager.h"
#include "connection.h"
#include "logindialog.h"
#include <QTextCodec>


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
    QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());
    if(!createConnection() || !createXml())//可以直接调用头文件中的函数
        return 0;
    DataManager w;//两个界面类都要定义
    LoginDialog dlg;
    //当在LoginDialog中运行QDialog::accept()函数时,会发送QDialog::Accepted信号,当接收到时,再调用DataManager界面
    if (dlg.exec() == QDialog::Accepted)
    {
        w.show();
        return a.exec();
    }
    else
    {
        return 0;
    }
    

}

 

 

 

  实验总结:

  本次实验主要是学习在Qt中对数据库,xml文件的建立,查询,删除,查找等操作,并且与Qt界面设计,图像绘制自定义视图等功能综合起来,是一个很不错的Qt学习sample。

 

  参考资料:

     http://www.yafeilinux.com/

 

  附录:

  实验工程code下载

 

 

 

posted on 2012-09-19 21:15  tornadomeet  阅读(38878)  评论(29编辑  收藏  举报

阿萨德发斯蒂芬