Пример "Calculator"
|
| Пользовательский ввод | Отображение на экране | Общая сумма | Add. Op. | Factor so Far | Mult. Op. | Ожидание операнда? |
|---|---|---|---|---|---|---|
| 0 | 0 | true | ||||
| 1 | 1 | 0 | false | |||
| 1 + | 1 | 1 | + | true | ||
| 1 + 2 | 2 | 1 | + | false | ||
| 1 + 2 ? | 2 | 1 | + | 2 | ? | true |
| 1 + 2 ? 3 | 3 | 1 | + | 2 | ? | false |
| 1 + 2 ? 3 - | 1.66667 | 1.66667 | - | true | ||
| 1 + 2 ? 3 - 4 | 4 | 1.66667 | - | false | ||
| 1 + 2 ? 3 - 4 = | -2.33333 | 0 | true |
Унарные операторы, такие как Sqrt, не требуют особой обработки; они могут быть выполнены немедленно как только операнд станет известен при щелчке по кнопке оператора.
QLineEdit *display;
enum { NumDigitButtons = 10 };
Button *digitButtons[NumDigitButtons];
};
В заключение, объявляем переменные, связанные с экраном и кнопками, используемыми для отображения цифр.
Calculator::Calculator(QWidget *parent)
: QDialog(parent)
{
sumInMemory = 0.0;
sumSoFar = 0.0;
factorSoFar = 0.0;
waitingForOperand = true;
В конструкторе инициализируем состояние калькулятора. Переменные pendingAdditiveOperator и pendingMultiplicativeOperator не нужно инициализировать явно, поскольку конструктор QString инициализирует их пустой строкой.
display = new QLineEdit("0");
display->setReadOnly(true);
display->setAlignment(Qt::AlignRight);
display->setMaxLength(15);
QFont font = display->font();
font.setPointSize(font.pointSize() + 8);
display->setFont(font);
Создаём QLineEdit изображающий экран калькулятора и настраиваем некоторые его свойства. В частности, мы установили режим только для чтения.
Также мы увеличили шрифт экрана на 8 пунктов.
for (int i = 0; i < NumDigitButtons; ++i) {
digitButtons[i] = createButton(QString::number(i), SLOT(digitClicked()));
}
Button *pointButton = createButton(tr("."), SLOT(pointClicked()));
Button *changeSignButton = createButton(tr("\261"), SLOT(changeSignClicked()));
Button *backspaceButton = createButton(tr("Backspace"), SLOT(backspaceClicked()));
Button *clearButton = createButton(tr("Clear"), SLOT(clear()));
Button *clearAllButton = createButton(tr("Clear All"), SLOT(clearAll()));
Button *clearMemoryButton = createButton(tr("MC"), SLOT(clearMemory()));
Button *readMemoryButton = createButton(tr("MR"), SLOT(readMemory()));
Button *setMemoryButton = createButton(tr("MS"), SLOT(setMemory()));
Button *addToMemoryButton = createButton(tr("M+"), SLOT(addToMemory()));
Button *divisionButton = createButton(tr("\367"), SLOT(multiplicativeOperatorClicked()));
Button *timesButton = createButton(tr("\327"), SLOT(multiplicativeOperatorClicked()));
Button *minusButton = createButton(tr("-"), SLOT(additiveOperatorClicked()));
Button *plusButton = createButton(tr("+"), SLOT(additiveOperatorClicked()));
Button *squareRootButton = createButton(tr("Sqrt"), SLOT(unaryOperatorClicked()));
Button *powerButton = createButton(tr("x\262"), SLOT(unaryOperatorClicked()));
Button *reciprocalButton = createButton(tr("1/x"), SLOT(unaryOperatorClicked()));
Button *equalButton = createButton(tr("="), SLOT(equalClicked()));
Для всех кнопок вызываем закрытую функцию createButton() с нужной текстовой меткой и слотом для соединения с кнопкой.
QGridLayout *mainLayout = new QGridLayout;
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
mainLayout->addWidget(display, 0, 0, 1, 6);
mainLayout->addWidget(backspaceButton, 1, 0, 1, 2);
mainLayout->addWidget(clearButton, 1, 2, 1, 2);
mainLayout->addWidget(clearAllButton, 1, 4, 1, 2);
mainLayout->addWidget(clearMemoryButton, 2, 0);
mainLayout->addWidget(readMemoryButton, 3, 0);
mainLayout->addWidget(setMemoryButton, 4, 0);
mainLayout->addWidget(addToMemoryButton, 5, 0);
for (int i = 1; i < NumDigitButtons; ++i) {
int row = ((9 - i) / 3) + 2;
int column = ((i - 1) % 3) + 1;
mainLayout->addWidget(digitButtons[i], row, column);
}
mainLayout->addWidget(digitButtons[0], 5, 1);
mainLayout->addWidget(pointButton, 5, 2);
mainLayout->addWidget(changeSignButton, 5, 3);
mainLayout->addWidget(divisionButton, 2, 4);
mainLayout->addWidget(timesButton, 3, 4);
mainLayout->addWidget(minusButton, 4, 4);
mainLayout->addWidget(plusButton, 5, 4);
mainLayout->addWidget(squareRootButton, 2, 5);
mainLayout->addWidget(powerButton, 3, 5);
mainLayout->addWidget(reciprocalButton, 4, 5);
mainLayout->addWidget(equalButton, 5, 5);
setLayout(mainLayout);
setWindowTitle(tr("Calculator"));
}
Компоновка обрабатывается одним QGridLayout. Вызов QLayout::setSizeConstraint() гарантирует, что виджет Calculator всегда выводится на экран с оптимальными размерами (его подсказка размера), не допуская изменения размеров калькулятора. Подсказка размера определяется размерами и политикой размера дочерних виджетов.
Большинство дочерних виджетов занимают только одну ячейку в компоновке-сетке. Поэтому нам нужно передавать только строку и столбец в QGridLayout::addWidget(). Виджеты display, backspaceButton, clearButton и clearAllButton занимают более одного столбца; для них мы должны также передавать объединение ячеек по вертикали (row span) и горизонтали (column span).
void Calculator::digitClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
int digitValue = clickedButton->text().toInt();
if (display->text() == "0" && digitValue == 0.0)
return;
if (waitingForOperand) {
display->clear();
waitingForOperand = false;
}
display->setText(display->text() + QString::number(digitValue));
}
Нажатие на одну из цифровых кнопок калькулятора приведёт к отправке сигнал clicked() кнопки, который переключит слот digitClicked().
Сначала, используя QObject::sender(), узнаем какая кнопка отправила сигнал. Эта функция возвратит отправителя в виде указателя на QObject. Поскольку нам известно, что отправитель является объектом Button, мы можем безопасно привести тип QObject. Можем использовать приведение типа в стиле C или же static_cast<>() C++, но в качестве защитного приёма программирования мы используем qobject_cast(). Преимуществом является то, что если объект имеет неверный тип, возвращается нулевой указатель. Крах из-за нулевых указателей значительно легче выявлять, чем падения от небезопасного приведения типов. Поскольку у нас есть кнопка, извлекаем оператор используя QToolButton::text().
В частности, слоту необходимо рассматривать два состояния. Если display содержит "0" и пользователь нажал на кнопку 0, будет глупо показывать "00". А если калькулятор находится в состоянии ожидания нового операнда, новая цифра является первой цифрой нового операнда; в этом случае сначала должен быть очищен результат предыдущих вычислений.
В заключение мы добавляем новую цифру к значению на экране.
void Calculator::unaryOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();
double result = 0.0;
if (clickedOperator == tr("Sqrt")) {
if (operand < 0.0) {
abortOperation();
return;
}
result = sqrt(operand);
} else if (clickedOperator == tr("x\262")) {
result = pow(operand, 2.0);
} else if (clickedOperator == tr("1/x")) {
if (operand == 0.0) {
abortOperation();
return;
}
result = 1.0 / operand;
}
display->setText(QString::number(result));
waitingForOperand = true;
}
Слот unaryOperatorClicked() вызывается всякий раз, когда нажата одна из кнопок унарных операторов. Кроме того указатель на нажатую кнопку можно получить с помощью QObject::sender(). Оператор извлекается из текста кнопки и сохраняется в clickedOperator. Операнд получаем из display.
Затем выполняем операцию. Если Sqrt применяется к отрицательному числу или 1/x - к нулю, то вызываем abortOperation(). Если все прошло нормально, выводим на экран результат операции в однострочное поле ввод и устанавливаем waitingForOperand в значение true. Это гарантирует, что если пользователь наберёт новую цифру, цифра будет рассматриваться как новый операнд, а ее добавится к текущему значению.
void Calculator::additiveOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();
Слот additiveOperatorClicked() вызывается тогда, когда пользователь нажимает на кнопку + или -.
Перед тем, как мы сможем действительно сделать что-либо с выбранным оператором, нужно обработать все незавершённые операции. Начинаем с мультипликативных операторов, поскольку они имеют более высокий приоритет по сравнению с аддитивными операторами:
if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}
Если ранее была нажата кнопка ? или ?, но = не нажималась позднее, то текущее значение на экране является верным операндом для оператора ? или ? и можно наконец выполнить операцию и обновить экран.
if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
display->setText(QString::number(sumSoFar));
} else {
sumSoFar = operand;
}
Если ранее была нажата кнопка + или -, то sumSoFar является левым операндом, а текущее значение на экране - правым операндом оператора. Если нет незавершённых аддитивных операторов, то sumSoFar просто устанавливается равным тексту на экране.
pendingAdditiveOperator = clickedOperator;
waitingForOperand = true;
}
В заключение, мы позаботимся об операторе, на кнопку которого только что нажали. Поскольку у нас ещё нет правостороннего операнда, то мы сохраняем нажатый оператор в переменной pendingAdditiveOperator. Позже, когда получим правый операнд, выполним операцию с sumSoFar в качестве левого операнда.
void Calculator::multiplicativeOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();
if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
} else {
factorSoFar = operand;
}
pendingMultiplicativeOperator = clickedOperator;
waitingForOperand = true;
}
Слот multiplicativeOperatorClicked() аналогичен слоту additiveOperatorClicked(). Нам не нужно волноваться о незавершённых аддитивных операторах, поскольку мультипликативные операторы имеют более высокий приоритет перед аддитивными операторами.
void Calculator::equalClicked()
{
double operand = display->text().toDouble();
if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}
if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
pendingAdditiveOperator.clear();
} else {
sumSoFar = operand;
}
display->setText(QString::number(sumSoFar));
sumSoFar = 0.0;
waitingForOperand = true;
}
Как и в additiveOperatorClicked(), начинаем с обработки всех незавершённых мультипликативных и аддитивных операторов. Затем выводим на экран sumSoFar и сбрасываем значение переменной в ноль. Сброс переменной в ноль необходим чтобы избежать повторного вычисления значения.
void Calculator::pointClicked()
{
if (waitingForOperand)
display->setText("0");
if (!display->text().contains("."))
display->setText(display->text() + tr("."));
waitingForOperand = false;
}
Слот pointClicked() добавляет десятичную точку к содержимому экрана display.
void Calculator::changeSignClicked()
{
QString text = display->text();
double value = text.toDouble();
if (value > 0.0) {
text.prepend(tr("-"));
} else if (value < 0.0) {
text.remove(0, 1);
}
display->setText(text);
}
Слот changeSignClicked() меняет знак значения на экране display. Если текущее значение положительно, добавляется знак минус; если текущее значение отрицательно, удаляется первый символ в значении (знак минуса).
void Calculator::backspaceClicked()
{
if (waitingForOperand)
return;
QString text = display->text();
text.chop(1);
if (text.isEmpty()) {
text = "0";
waitingForOperand = true;
}
display->setText(text);
}
backspaceClicked() удаляет самый правый символ на экране. Если имеется пустая строка, показываем "0" и устанавливаем waitingForOperand в значение true.
void Calculator::clear()
{
if (waitingForOperand)
return;
display->setText("0");
waitingForOperand = true;
}
Слот clear() сбрасывает значение текущего операнда в ноль. Это эквивалентно нажатию на кнопку Backspace столько раз, чтобы стереть весь операнд.
void Calculator::clearAll()
{
sumSoFar = 0.0;
factorSoFar = 0.0;
pendingAdditiveOperator.clear();
pendingMultiplicativeOperator.clear();
display->setText("0");
waitingForOperand = true;
}
Слот clearAll() сбрасывает калькулятор в его изначальное состояние.
void Calculator::clearMemory()
{
sumInMemory = 0.0;
}
void Calculator::readMemory()
{
display->setText(QString::number(sumInMemory));
waitingForOperand = true;
}
void Calculator::setMemory()
{
equalClicked();
sumInMemory = display->text().toDouble();
}
void Calculator::addToMemory()
{
equalClicked();
sumInMemory += display->text().toDouble();
}
Слот clearMemory() стирает сумму, хранимую в памяти, readMemory() - выводит на экран сумму в качестве операнда, setMemory() - заменяет сумму в памяти на текущую сумму, а addToMemory() - добавляет текущее значение к значению в памяти. В случае setMemory() и addToMemory() мы сначала вызываем equalClicked() для обновления sumSoFar и значения на экране.
Button *Calculator::createButton(const QString &text, const char *member)
{
Button *button = new Button(text);
connect(button, SIGNAL(clicked()), this, member);
return button;
}
Закрытая функция createButton() вызывается из конструктора для создания кнопок калькулятора.
void Calculator::abortOperation()
{
clearAll();
display->setText(tr("####"));
}
Закрытая функция abortOperation() вызывается всякий раз, когда вычисление завершится неудачно. Она сбросит состояние калькулятора и выведет на экран "####".
bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
{
if (pendingOperator == tr("+")) {
sumSoFar += rightOperand;
} else if (pendingOperator == tr("-")) {
sumSoFar -= rightOperand;
} else if (pendingOperator == tr("\327")) {
factorSoFar *= rightOperand;
} else if (pendingOperator == tr("\367")) {
if (rightOperand == 0.0)
return false;
factorSoFar /= rightOperand;
}
return true;
}
Закрытая функция calculate() выполняет бинарную операцию. Правый операнд предоставляется rightOperand. Для аддитивных операторов левым операндом является sumSoFar; для мультипликативных операторов левым оператором является factorSoFar. Функция возвращает false в случае деления на ноль.
Давайте теперь рассмотрим класс Button:
class Button : public QToolButton
{
Q_OBJECT
public:
Button(const QString &text, QWidget *parent = 0);
QSize sizeHint() const;
};
Класс Button имеет удобный конструктор, который принимает текстовую метку и родительский виджет, а также она переопределяет QWidget::sizeHint() чтобы предоставить больше пространства вокруг текста чем обычно предоставляет QToolButton.
Button::Button(const QString &text, QWidget *parent)
: QToolButton(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
setText(text);
}
Внешний вид кнопок определяется компоновкой виджета калькулятора посредством размера и политики размера дочерних виджетов компоновки. Вызов функции setSizePolicy() в конструкторе гарантирует, что кнопка будет растянута по горизонтали чтобы заполнить все доступное пространство; по умолчанию, инструментальные кнопки QToolButton не растягиваются для заполнения всего доступного пространства. Без этого вызова разные кнопки в одном и том же столбце будут иметь разную ширину.
QSize Button::sizeHint() const
{
QSize size = QToolButton::sizeHint();
size.rheight() += 20;
size.rwidth() = qMax(size.width(), size.height());
return size;
}
В sizeHint() мы стараемся вернуть размер, который лучше всего подходит для большинства кнопок. Мы используем повторно подсказку размера базового класса (QToolButton), но модифицировали её следующим образом:
Этим обеспечиваем квадратную форму кнопок цифр и операторов с большинством шрифтов, без усечения текста в кнопках Backspace, Clear и Clear All buttons.
Снимок экрана ниже показывает, как виджет Calculator будет выглядеть, если мы не установим горизонтальную политику размера QSizePolicy::Expanding в конструкторе и если мы не переопределим QWidget::sizeHint().

| Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) | Торговые марки | Qt 4.5.3 |
|
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |