пятница, 10 апреля 2009 г.

Таблица параметров постпечатной обработки

На самом деле, у всех вариантов формы "параметры изделия" (предыдущий пост) есть один существенный недостаток: если, к примеру, на предприятии используется некая операция постпечатной обработки, отсутствующая на форме, то пользователь не сможет добавить ее, или, другой пример, если присутствует некая операция, которая не используется на данном предприятии - пользователь не сможет убрать ее с формы.
Есть два решения этой проблемы - потратить много-много лет на тщательное изучение типографского дела, итогом чего станет совершенная форма, на которой будут учтены все возможные полиграфические операции, либо же реализовать форму с возможностью настройки "под себя".

1. Описание
Все параметры изделия можно разделить на два типа - обязательные и возможные. К первым относятся тираж изделия, его формат, красочность и бумага. Прочие параметры - ламинация, биговка, тип скрепления - относятся ко второму типу, которые и требуют пользовательской настройки. Объединим эти параметры в такой таблице...

В первой колонке - название операции. Когда пользователь выбирает определенную операцию (активирует чекбокс), открываются строки с уточняющими параметрами (к примеру, тип, красочность и вид УФ-лака). Общая сводка параметров данной операции отображается во второй колонке в строке с ее названием (первоначально здесь - значение по умолчанию). Для удобства реализовано два режима просмотра таблицы - все строки и строки только отмеченных операций.
Что касается пользовательской настройки - дополнить таблицу новыми операциями, или удалить ненужные, пользователь может отредактировав xml-файл, из которого загружается таблица при старте программы.



2. Реализация
Создать подобную таблицу в Qt можно несколькими способами. К примеру, на основе TableWidget. Вначале, реализуем несколько собственных классов: PostpressTableWidgetItem - наследник от QTableWidgetItem, в который добавим переменную int parent_row, а также PostpressComboBox и PostpressLineEdit, наследующие от QComboBox и QLineEdit и в которые добавим следующие переменные - int parent_row, int pos.
Теперь пишем загрузчик таблицы.

// открываем xml-файл...
// заполняем таблицу
int row = 0; // номер текущей строки
int parent_row = 0; // номер "родительской" строки

while (!child.isNull())
{
// читаем название операции и значение по умолчанию
QString name = child.firstChildElement("name").text();

QString default_value = child.firstChildElement("default_value").text();

// добавляем строку

m_ui->tableWidget_postpress->insertRow(row);

// заполняем первую колонку

PostpressTableWidgetItem *item = new PostpressTableWidgetItem();

item->setText(name);

item->setCheckState(Qt::Unchecked);

item->setParentRow(parent_row);

m_ui->tableWidget_postpress->setItem(row, 0, item);

// заполняем вторую колонку

PostpressTableWidgetItem *item_2 = new PostpressTableWidgetItem();

item_2->setText(default_value);

item_2->setParentRow(parent_row);

m_ui->tableWidget_postpress->setItem(row, 1, item_2);

// читаем параметры данной операции

QDomElement param = child.firstChildElement("param");

while (!param.isNull())

{

// извлекаем название параметра и его возможные значения

QString param_name = param.firstChildElement("name").text();

QString param_value = param.firstChildElement("value").text();

m_ui->tableWidget_postpress->insertRow(++row);

// заполняем колонку с названием параметра

PostpressTableWidgetItem *item_3 = new PostpressTableWidgetItem();

item_3->setText(param_name);

// задаем родительскую строку -

// для строки параметра операции родительской строкой

// будет строка с названием операции

item_3->parent_row = parent_row;

m_ui->tableWidget_postpress->setItem(row, 0, item_3);

// заполняем колонку с возможными значениями параметра

QString temp = param_value;

QStringList param_value_list = temp.split(",");

if (!param_value_list.empty())

{

if (param_value_list.size() > 1)

{

// если у параметра несколько возможных значений

// устанавливаем в колонку комбобокс

PostpressComboBox *cb = new PostpressComboBox (parent_row, row - parent_row - 1);

cb->insertItems(0, param_value_list);

connect(cb, SIGNAL(currentIndexChanged(QString)), this, SLOT(UpdatePostpressComboBox(QString)));

m_ui->tableWidget_postpress->setCellWidget(row, 1, cb);

}

else

{

// если возможных значений одно

// устанавливаем в колонку лайнэдит

PostpressLineEdit *cb = new PostpressLineEdit (parent_row, row - parent_row - 1);

cb->setText(param_value_list[0]);

connect(cb, SIGNAL(textChanged(QString)), this, SLOT(UpdatePostpressLineEdit(QString)));

m_ui->tableWidget_postpress->setCellWidget(row, 1, cb);

}

}

// прячем строку параметра операции

m_ui->tableWidget_postpress->hideRow(row);

param = param.nextSiblingElement("param");

}

++row;

parent_row = row;

child = child.nextSiblingElement("operation");

} // завершение цикла while (!child.isNull())


Что касается слотов - см. исходники.

понедельник, 17 ноября 2008 г.

Эволюция формы "параметры изделия"





и каждый раз я говорил себе "вот теперь - точно оно!"  


четверг, 6 ноября 2008 г.

Система калькуляции заказов

Приступили к работе над калькулятором. На данный момент остановились на следующей системе - расчет на основе данных по оборудованию + скидки/наценки по конкретным клиентам. Ниже - пример расчета, основанный на оборудовании. Критика - приветствуется ) 

Операция
Время переналадки
Скорость
Учетная стоимость/час
Учетный материал 1
Кол-во на приладку
Расход на единицу
Техотходы %
...
Исполнитель
Учетная ставка

/
ПРИМЕР
Фальцовка автоматическая
Приладка 15 минут
Скорость 5000/час
Стоимость 1500/час
Техотходы 5%
Оператор фальцаппарта 100р./час

Печать SM-74-4
Переналадка 30 минут
Скорость 10000/час
Стоимость 10000р./час
Бумага на приладку 250 листов
Техотходы 5%.
Печатник 250р./час

Вывод форм Suprasetter
Скорость 12/час
Стоимость 1000 р./час
Формы 300р./шт
Оператор 200р./час
Техотходы 2%

/
ПРИМЕР
Заказ газета А2 4+4 1 фальц 10000 экз.

Калькуляция

Вывод форм тираж 8 шт.
Время операции 1 час.
Стоимость
Формы 2400
Техотходы 50
Оператор 200
Работа оборудования 1000
Итого 3650 р.

Печать SM74-4 Выходной тираж 10100 экз.
2 переналадки
20200 оттисков
Бумага на приладку 500 л
Бумага на техотходы 1000 л.
Всего бумаги 21700 л. * 1,5 р. = 32250 р.
Время печати 3 часа
Стоимость операции 63000 р.

Фальцовка Выходной тираж 10050 экз.
Время операции 2,5 часа
Техотходы 50 экз.
Стоимость 4000 р.

воскресенье, 28 сентября 2008 г.

Реализация раздела "Заказчики". Часть 3

3. "Корзина"

Таблица "корзина" реализуется аналогично таблице "все заказчики", с единственным отличием - в условии фильтрации proxyModel:

proxyModel->setFilterRegExp(QRegExp("0", Qt::CaseInsensitive, QRegExp::FixedString)); 
proxyModel->setFilterKeyColumn(10);

- после чего в таблице "корзина" будут отображаться записи, в которых значение колонки "active" равно "0". Таким образом, все что нужно сделать для того, чтобы переместить запись заказчика в корзину - изменить значение "active":

customerform *page = (customerform*) ui.tabWidget->currentWidget(); // получаем указатель на текущую закладку в tabWidget заказчиков
QSqlRecord record = model->record(page->mapper->currentIndex()); // получаем из модели запись текущего заказчика
record.setValue("active", "0"); // устанавливаем active в ноль
model->setRecord(page->mapper->currentIndex(), record); // обновляем запись текущего заказчика
model->submitAll();
ui.tabWidget->removeTab(ui.tabWidget->currentIndex()); // удаляем текущую вкладку 

Востановим запись из корзины:

QModelIndex index = ui.tableView->currentIndex(); // индекс выделенной строки в таблице "корзина"
QModelIndex index2 = proxyModel->mapToSource(index); // переводим его из proxyModel в индекс исходной модели
model->setData(cust_model->index(index2.row(), 9), "1"); // изменяем active 
model->submitAll(); 

Удаляем запись навсегда:

QModelIndex index = ui.tableView->currentIndex(); // индекс выделенной строки в таблице "корзина"
QModelIndex index2 = proxyModel->mapToSource(index); // переводим в индекс исходной модели
model->removeRow(index2.row()); // удаляем строку из модели
model->submitAll(); // обновляем базу данных

пятница, 26 сентября 2008 г.

Драйвера в Qt

В стандартной сборке Qt часть драйверов различных модулей внедрены как плагины и при компиляции проекта не включаются в исполняемый файл. Это касается и драйвера SQLite. Т.е. при запуске приложения в системе, где бибилиотека Qt не установлена, нас ждут ошибки, или сообщение - "драйвер не найден". Чтобы решить эту проблему, мы должны либо добавить необходимый драйвер (в случае с SQLite - это qsqlite4.dll, которую надо поместить в директорию sqldrivers в папке с exe-файлом), либо пересобрать Qt, задав с помощью configure включение драйвера непосредственно в библиотеку Qt (для SQLite - это "-qt-sql-sqlite").

вторник, 23 сентября 2008 г.

Реализация раздела "Заказчики". Часть 2

2. Форма для добавления и редактирования данных по заказчикам.

Связать модель с такими виджетами, как TableView или ListView - легко и просто, достаточно только вызвать setModel() и проблема решена. Но как быть, если виджет представляет из себя форму, на которой размещены комбобоксы, лайнедиты и т.п.? Как установить связь менжду этими элементами и SQL-данными? 
Реализовать подобное можно двумя способами: 1) не используя QDataWidgetMapper и 2) используя QDataWidgetMapper. Далее - описание второго способа.
Итак, есть виджет, на котором размещены несколько lineEdit. Каждый из них нам нужно связать с той или иной колонкой из таблицы "customers": 
lineEdit - название заказчика, lineEdit_1 - город заказчика, ну и т.д. 

QDataWidgetMapper *mapper = new QDataWidgetMapper(this); // создаем в конструкторе виджета маппер
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); // определяем стратегию обновления данных
mapper->setModel(model); // назначаем модель с таблицей "customers"

Используя метод addMapping(), установим связь между  lineEdit и колонками таблицы:

mapper->addMapping(ui.lineEdit, 0); // имя заказчика
mapper->addMapping(ui.lineEdit_1, 1); // город
mapper->addMapping(ui.lineEdit_2, 2); // улица
mapper->addMapping(ui.lineEdit_3, 3); // офисный телефон
mapper->addMapping(ui.lineEdit_4, 4); // факс
mapper->addMapping(ui.lineEdit_5, 5); // имя контактного лица
mapper->addMapping(ui.lineEdit_6, 7); // мобильный номер
mapper->addMapping(ui.lineEdit_7, 8); // майл
mapper->addMapping(ui.textEdit, 9); // примечание

После того, как мы связали лайнедиты с табличными колонками, осталось только назначить мапперу табличный индекс. Продемонстрируем это на примере открытия записи заказчика из таблицы "Все заказчики" - метод OpenCustomer(QModelIndex), назначенный в качестве слота сигналу doubleClicked виджета tableView:

connect(ui.tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OpenCustomer(QModelIndex)));

После двойного клика на строке виджета tableView в метод OpenCustomer передается текущий индекс из модели, которая была задана виджету. Остается только передать этот индекс маппер, но... В маппер в качестве модели мы назначили QSqlTableModel, а виджету tableView - QSortFilterProxyModel. Чтобы избежать ошибки, необходимо перевести индекс из QSortFilterProxyModel в индекс QSqlTableModel:

QModelIndex index2 = proxyModel->mapToSource(index); // переводим текущий index из QSortFilterProxyModel в QSqlTableModel
mapper->setCurrentModelIndex(index2); // назначаем индекс мапперу

Теперь в лайнедитах отображаются данные из строки, выделенной в виджете tableView. Выше мы выбрали "ручную" стратегию сохранения, таким образом, для того, чтобы изменения в лайнедитах вступили в силу - необходимо вызвать метод submit().

понедельник, 22 сентября 2008 г.

Реализация раздела "Заказчики". Часть 1

Задачи:

1. Стартовая страница с таблицей "Все заказчики", в которой отображаются записи из таблицы "customers" со значением "active" == 1.
2. Форма для добавления и редактирования данных по заказчикам.
3. "Корзина" с таблицей удаленных заказчиков, где отображаются записи со значением "active" == 0. 

Реализация:

1. Таблица "Все заказчики".

Вся информация по заказчикам хранится в базе данных "customers.s3db", в таблице "customers". Для отображения записей воспользуемся модулем QSqlTableModel, реализующим технологию модель/вид между SQL-таблицами и такими виджетами, как QListView, QTableView и QTreeView. 

QSqlTableModel *model = new QSqlTableModel;
model->setTable("customers");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();

setEditStrategy() определяет метод обновления данных в sql-таблице после их изменения в виджете: 
QSqlTableModel::OnFieldChange - данные обновляются немедленно; 
QSqlTableModel::OnRowChange - новые данные передаются в таблицу после того, как пользователь выделяет другую строку; 
QSqlTableModel::OnManualSubmit - изменения вносятся в момент вызова метода submitAll().
Назначим созданную модель виджету QTableView -

tableView->setModel(model);

- после чего виджет tableView будет отображать все строки и колонки из таблицы "customers". 
Скрыть некоторые колонки можно как на уровне модели (model->removeColumn(1)), так и на уровне виджета (tableView->hideColumn(1)). Мы воспользуемся вторым методом, и спрячем часть колонок, оставив только название заказчика, имя контактного лица, телефон и емайл.

tableView->hideColumn(1); // прячем название города
tableView->hideColumn(2); // прячем название улицы и т.д.

Колонка "active" в таблице "customers" отображает текущее состояние записи о заказчике: если запись удалениа - "active" принимает значение 0, и запись перемещается в таблицу "Корзина", с возможностью восстановления, или окончательного удаления; если значение "active" == 1, то запись отображается в таблице "Все заказчики".
Есть несколько способов реализации фильтрации строк. Опять же на уровне модели (model->setFilter("active = 1")), или с помощью модуля QSortFilterProxyModel, позволяющим установить правила для фильтрации и сортировки данных между моделью и видом.
Зададим фильтрацию по значению "active" для таблицы "Все заказчики", используя QSortFilterProxyModel:

proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model); // указываем в качестве исходной модели QSqlTableModel с таблицей "customers". 
proxyModel->setFilterRegExp(QRegExp("1", Qt::CaseInsensitive, QRegExp::FixedString)); // "верное" значение фильтрации
proxyModel->setFilterKeyColumn(10); // задаем номер колонки, по которой будет происходить фильтрация

После того, как правило фильтрации установленно, назначаем proxyModel виджету:
tableView->setModel(proxyModel); 

В итоге код для таблицы "Все заказчики" будет выглядеть вот так:

proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->setFilterRegExp(QRegExp("1", Qt::CaseInsensitive, QRegExp::FixedString));
proxyModel->setFilterKeyColumn(10);
ui.tableView->setModel(proxyModel);
ui.tableView->hideColumn(1);
ui.tableView->hideColumn(2);
ui.tableView->hideColumn(3);
ui.tableView->hideColumn(4);
ui.tableView->hideColumn(9);
ui.tableView->hideColumn(10);