У даному уроці хотілося б розповісти про те, як можна кастомізувати зовнішній вигляд об'єкта Calendar у Qt Qml . Підправити, наприклад, кольори, шрифт, а також красиво вписати його в діалогове вікно для вибору дати. Тому визначимося з тим, як має працювати наша програма і як вона має виглядати:
- У головному вікні програми буде знаходитись стандартна кнопка, на якій відображається дата (хоча якщо хочете, то можете і її кастомізувати );
- При натисканні на кнопку відкривається діалогове вікно, в якому розташовується Calendar і дві кнопки ("Ok" і "Cancel"). У Calendar встановлюється дата, що була вказана на кнопці;
- При натисканні на кнопку Cancel нічого не відбувається, а діалогове вікно просто закривається;
- При натисканні на кнопку "OK" діалогове вікно закривається, а на кнопці головного вікна з'явиться дата, яка була вибрана в календарі.
Вважаю, що Ви вже встигли помітити, що кастомізація інтерфейсу програми в Qt QML проходить однаково, як для Desktop версій, так і для Android. Інакше б розробка під Qt не вважалася кросплатформною. Отже, я не буду сильно вдаватися в різницю налаштування відображення діалогового вікна для Desktop версії або для Android версії. Цей момент я пояснював в уроці створення кастомізованого діалогового вікна .
Структура проекта для Custom Calendar
Для демонстрації прикладу було створено новий проект із наступною структурою:
- QmlCalendarCustom.pro - профайл проекту;
- main.cpp - основний файл вихідних кодів;
- main.qml - основний файл інтерфейсу на QML;
- left_arrow.png - не натиснута стрілка вліво;
- left_arrow_disable.png - натиснута стрілка вліво;
- right_arrow.png - не натиснута стрілка вправо;
- right_arrow_disable.png - натиснута стрілка вправо.
Стрілки
Застосовую наступні зображення для кастомізації перегортання місяців у Calendar.
main.cpp
Цей файл створюється за замовчуванням, але я наведу його програмний код, щоб не було плутанини.
#include <QApplication> #include <QQmlApplicationEngine> int main(int argc, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
main.qml
А ось все неподобство творитимемо саме в даному файлі. У ньому ж створимо Custom Calendar, який буде встановлювати дату на кнопку.
import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 ApplicationWindow { visible: true width: 640 height: 480 title: qsTr("Hello World") /* Создадим переменную для хранения даты, чтобы не заморачиваться * с конвертацией типов * */ property var tempDate: new Date(); Button { id: button // Устанавливаем текущую дату при запуске приложения на кнопку text: Qt.formatDate(tempDate, "dd.MM.yyyy"); anchors.centerIn: parent // Центруем кнопку в окне // По клику на кнопку запускаем диалоговое окно черз кастомную функцию onClicked: dialogCalendar.show(tempDate) } Dialog { id: dialogCalendar // Задаём размеры диалогового окна width: 250 height: 300 // Создаем контент диалогового окна contentItem: Rectangle { id: dialogRect color: "#f7f7f7" // Первым идёт кастомный календарь Calendar { id: calendar // Размещаем его в верхней части диалога и растягиваем по ширине anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: row.top // Стилизуем Календарь style: CalendarStyle { // Стилизуем navigationBar navigationBar: Rectangle { /* Он будет состоять из прямоугольника, * в котором будет располагаться две кнопки и label * */ height: 48 color: "#f7f7f7" /* Горизонтальный разделитель, * который отделяет navigationBar от поля с числами * */ Rectangle { color: "#d7d7d7" height: 1 width: parent.width anchors.bottom: parent.bottom } // Кнопка промотки месяцев назад Button { id: previousMonth width: parent.height - 8 height: width anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 8 /* По клику по кнопке вызываем функцию * календаря, которая отматывает месяц назад * */ onClicked: control.showPreviousMonth() // Стилизуем кнопку style: ButtonStyle { background: Rectangle { // Окрашиваем фон кнопки color: "#f7f7f7" /* И помещаем изображение, у которго будет * два источника файлов в зависимости от того * нажата кнопка или нет */ Image { source: control.pressed ? "left_arrow_disable.png" : "left_arrow.png" width: parent.height - 8 height: width } } } } // Помещаем стилизованный label Label { id: dateText /* Забираем данные из title календаря, * который в данном случае не будет виден * и будет заменён данным label */ text: styleData.title color: "#34aadc" elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter font.pixelSize: 16 anchors.verticalCenter: parent.verticalCenter anchors.left: previousMonth.right anchors.leftMargin: 2 anchors.right: nextMonth.left anchors.rightMargin: 2 } // Кнопка промотки месяцев вперёд Button { id: nextMonth width: parent.height - 8 height: width anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right /* По клику по кнопке вызываем функцию * календаря, которая отматывает месяц назад * */ onClicked: control.showNextMonth() // Стилизуем кнопку style: ButtonStyle { // Окрашиваем фон кнопки background: Rectangle { color: "#f7f7f7" /* И помещаем изображение, у которго будет * два источника файлов в зависимости от того * нажата кнопка или нет */ Image { source: control.pressed ? "right_arrow_disable.png" : "right_arrow.png" width: parent.height - 8 height: width } } } } } // Стилизуем отображением квадратиков с числами месяца dayDelegate: Rectangle { anchors.fill: parent anchors.margins: styleData.selected ? -1 : 0 // Определяем цвет в зависимости от того, выбрана дата или нет color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent" // Задаём предопределённые переменные с цветами, доступные только для чтения readonly property color sameMonthDateTextColor: "#444" readonly property color selectedDateColor: "#34aadc" readonly property color selectedDateTextColor: "white" readonly property color differentMonthDateTextColor: "#bbb" readonly property color invalidDateColor: "#dddddd" // Помещаем Label для отображения числа Label { id: dayDelegateText text: styleData.date.getDate() // Устанавливаем число в текущий квадрат anchors.centerIn: parent horizontalAlignment: Text.AlignRight font.pixelSize: 10 // Установка цвета color: { var theColor = invalidDateColor; // Устанавливаем невалидный цвет текста if (styleData.valid) { /* Определяем цвет текста в зависимости от того * относится ли дата к выбранному месяцу или нет * */ theColor = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor; if (styleData.selected) // Перекрашиваем цвет текста, если выбрана данная дата в календаре theColor = selectedDateTextColor; } theColor; } } } } } // Делаем панель с кнопками Row { id: row height: 48 anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom // Кнопка для закрытия диалога Button { id: dialogButtonCalCancel anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width / 2 - 1 style: ButtonStyle { background: Rectangle { color: control.pressed ? "#d7d7d7" : "#f7f7f7" border.width: 0 } label: Text { text: qsTr("Cancel") font.pixelSize: 14 color: "#34aadc" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } // По нажатию на кнопку - просто закрываем диалог onClicked: dialogCalendar.close() } // Вертикальный разделитель между кнопками Rectangle { id: dividerVertical width: 2 anchors.top: parent.top anchors.bottom: parent.bottom color: "#d7d7d7" } // Кнопка подтверждения выбранной даты Button { id: dialogButtonCalOk anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width / 2 - 1 style: ButtonStyle { background: Rectangle { color: control.pressed ? "#d7d7d7" : "#f7f7f7" border.width: 0 } label: Text { text: qsTr("Ok") font.pixelSize: 14 color: "#34aadc" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } /* По клику по кнопке сохраняем выбранную дату во временную переменную * и помещаем эту дату на кнопку в главном окне, * после чего закрываем диалог */ onClicked: { tempDate = calendar.selectedDate button.text = Qt.formatDate(tempDate, "dd.MM.yyyy"); dialogCalendar.close(); } } } } /* Данная функция необходима для того, чтобы * установить дату с кнопки в календарь, * иначе календарь откроется с текущей датой */ function show(x){ calendar.selectedDate = x dialogCalendar.open() } } }
Підсумок
В результаті у Вас має отримати додаток з наступним діалоговим вікном, в якому присутній Custom Calendar. Також у відеоуроці наведено демонстрацію роботи програми з коментарями за програмним кодом.
Все очень круто, больше спасибо.
Подскажите пжлст что такое styleData?
Это какой то объект из dialogCalendar
Если я захочу другое отображение колендаря, мне нужно будет перегружать функции в новом классе?
Добрый день!
Ага, это внутренний объект стилей.
Спасибо)
Привет. Вопрос именно по старой версии календаря(controls 1.4).
1. Подскажите как календарь определяет выбранный день.
2. В styleData.selected возвращается true если дата выбрана, но я так и не нашёл как вручную выбирается дата...
3. ...если работать с onClicked в области делегата, то автоматический механизм выбора ячейки календаря перестаёт работать. Как быть?
Подытожу. Без onClicked подсветка выбранного дня работает, но если мне нужно производить какие нибудь действия по нажатии на ячейку через onClicked, то всё ломается. Это же касается и onPressed, onReleased и пр.
П.С. Пробовал в onClicked менять градиент выбранного дня - получилось, но мозгов допилить изменение градиента остальных дней не хватает.
Отвечу сам себе.
Чтоб подсветка ячейки менялась при нажатии применил следующее:
Но пока что имеются проблемы с правильным переходом на предыдущий/следующий месяц и получением правильной даты при нажатии на дату не находящуюся в текущем месяце.
.
Вы перекрываете события этим MouseArea, попробуйте пропихнуть всё дальше включив следующее свойство