Evgenii Legotckoi
22 октября 2015 г. 22:40

QML - Урок 006. Custom Calendar in Qt QML or Qt QML Android

В данном уроке хотелось бы рассказать о том, каким образом можно кастомизировать внешний вид объекта Calendar в Qt Qml . Подправить например цвета, шрифт, а также красиво вписать его в диалоговое окно для выбора даты. Поэтому определимся с тем, как должно работать наше приложение и как оно должно выглядеть:

  1. В главном окне приложения будет находиться стандартная кнопка, на которой отображается дата (хотя если хотите, то можете и её кастомизировать );
  2. При нажатии на кнопку открывается диалоговое окно в котором располагается Calendar и две кнопки ("Ok" и "Cancel"). В Calendar устанавливается дата, которая была указана на кнопке;
  3. При нажатии на кнопку "Cancel" ничего не происходит, а диалоговое окно просто закрывается;
  4. При нажатии на кнопку "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

Данный файл создаётся по умолчанию, но я приведу его программный код, чтобы не было путаницы.

  1. #include <QApplication>
  2. #include <QQmlApplicationEngine>
  3.  
  4. int main(int argc, char *argv[])
  5. {
  6. QApplication app(argc, argv);
  7.  
  8. QQmlApplicationEngine engine;
  9. engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
  10.  
  11. return app.exec();
  12. }

main.qml

А вот всё безобразие будем творить именно в данном файле. В нём же создадим Custom Calendar, который будет устанавливать дату на кнопку.

  1. import QtQuick 2.5
  2. import QtQuick.Controls 1.4
  3. import QtQuick.Controls.Styles 1.4
  4. import QtQuick.Dialogs 1.2
  5.  
  6. ApplicationWindow {
  7. visible: true
  8. width: 640
  9. height: 480
  10. title: qsTr("Hello World")
  11.  
  12. /* Создадим переменную для хранения даты, чтобы не заморачиваться
  13. * с конвертацией типов
  14. * */
  15. property var tempDate: new Date();
  16.  
  17. Button {
  18. id: button
  19. // Устанавливаем текущую дату при запуске приложения на кнопку
  20. text: Qt.formatDate(tempDate, "dd.MM.yyyy");
  21. anchors.centerIn: parent // Центруем кнопку в окне
  22.  
  23. // По клику на кнопку запускаем диалоговое окно черз кастомную функцию
  24. onClicked: dialogCalendar.show(tempDate)
  25.  
  26. }
  27.  
  28. Dialog {
  29. id: dialogCalendar
  30. // Задаём размеры диалогового окна
  31. width: 250
  32. height: 300
  33.  
  34. // Создаем контент диалогового окна
  35. contentItem: Rectangle {
  36. id: dialogRect
  37. color: "#f7f7f7"
  38.  
  39. // Первым идёт кастомный календарь
  40. Calendar {
  41. id: calendar
  42. // Размещаем его в верхней части диалога и растягиваем по ширине
  43. anchors.top: parent.top
  44. anchors.left: parent.left
  45. anchors.right: parent.right
  46. anchors.bottom: row.top
  47.  
  48. // Стилизуем Календарь
  49. style: CalendarStyle {
  50.  
  51. // Стилизуем navigationBar
  52. navigationBar: Rectangle {
  53. /* Он будет состоять из прямоугольника,
  54. * в котором будет располагаться две кнопки и label
  55. * */
  56. height: 48
  57. color: "#f7f7f7"
  58.  
  59. /* Горизонтальный разделитель,
  60. * который отделяет navigationBar от поля с числами
  61. * */
  62. Rectangle {
  63. color: "#d7d7d7"
  64. height: 1
  65. width: parent.width
  66. anchors.bottom: parent.bottom
  67. }
  68.  
  69. // Кнопка промотки месяцев назад
  70. Button {
  71. id: previousMonth
  72. width: parent.height - 8
  73. height: width
  74. anchors.verticalCenter: parent.verticalCenter
  75. anchors.left: parent.left
  76. anchors.leftMargin: 8
  77.  
  78. /* По клику по кнопке вызываем функцию
  79. * календаря, которая отматывает месяц назад
  80. * */
  81. onClicked: control.showPreviousMonth()
  82.  
  83. // Стилизуем кнопку
  84. style: ButtonStyle {
  85. background: Rectangle {
  86. // Окрашиваем фон кнопки
  87. color: "#f7f7f7"
  88. /* И помещаем изображение, у которго будет
  89. * два источника файлов в зависимости от того
  90. * нажата кнопка или нет
  91. */
  92. Image {
  93. source: control.pressed ? "left_arrow_disable.png" : "left_arrow.png"
  94. width: parent.height - 8
  95. height: width
  96. }
  97. }
  98. }
  99. }
  100.  
  101. // Помещаем стилизованный label
  102. Label {
  103. id: dateText
  104. /* Забираем данные из title календаря,
  105. * который в данном случае не будет виден
  106. * и будет заменён данным label
  107. */
  108. text: styleData.title
  109. color: "#34aadc"
  110. elide: Text.ElideRight
  111. horizontalAlignment: Text.AlignHCenter
  112. font.pixelSize: 16
  113. anchors.verticalCenter: parent.verticalCenter
  114. anchors.left: previousMonth.right
  115. anchors.leftMargin: 2
  116. anchors.right: nextMonth.left
  117. anchors.rightMargin: 2
  118. }
  119.  
  120. // Кнопка промотки месяцев вперёд
  121. Button {
  122. id: nextMonth
  123. width: parent.height - 8
  124. height: width
  125. anchors.verticalCenter: parent.verticalCenter
  126. anchors.right: parent.right
  127.  
  128. /* По клику по кнопке вызываем функцию
  129. * календаря, которая отматывает месяц назад
  130. * */
  131. onClicked: control.showNextMonth()
  132.  
  133. // Стилизуем кнопку
  134. style: ButtonStyle {
  135. // Окрашиваем фон кнопки
  136. background: Rectangle {
  137. color: "#f7f7f7"
  138. /* И помещаем изображение, у которго будет
  139. * два источника файлов в зависимости от того
  140. * нажата кнопка или нет
  141. */
  142. Image {
  143. source: control.pressed ? "right_arrow_disable.png" : "right_arrow.png"
  144. width: parent.height - 8
  145. height: width
  146. }
  147. }
  148. }
  149. }
  150. }
  151.  
  152.  
  153. // Стилизуем отображением квадратиков с числами месяца
  154. dayDelegate: Rectangle {
  155. anchors.fill: parent
  156. anchors.margins: styleData.selected ? -1 : 0
  157. // Определяем цвет в зависимости от того, выбрана дата или нет
  158. color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent"
  159.  
  160. // Задаём предопределённые переменные с цветами, доступные только для чтения
  161. readonly property color sameMonthDateTextColor: "#444"
  162. readonly property color selectedDateColor: "#34aadc"
  163. readonly property color selectedDateTextColor: "white"
  164. readonly property color differentMonthDateTextColor: "#bbb"
  165. readonly property color invalidDateColor: "#dddddd"
  166.  
  167. // Помещаем Label для отображения числа
  168. Label {
  169. id: dayDelegateText
  170. text: styleData.date.getDate() // Устанавливаем число в текущий квадрат
  171. anchors.centerIn: parent
  172. horizontalAlignment: Text.AlignRight
  173. font.pixelSize: 10
  174.  
  175. // Установка цвета
  176. color: {
  177. var theColor = invalidDateColor; // Устанавливаем невалидный цвет текста
  178. if (styleData.valid) {
  179. /* Определяем цвет текста в зависимости от того
  180. * относится ли дата к выбранному месяцу или нет
  181. * */
  182. theColor = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor;
  183. if (styleData.selected)
  184. // Перекрашиваем цвет текста, если выбрана данная дата в календаре
  185. theColor = selectedDateTextColor;
  186. }
  187. theColor;
  188. }
  189. }
  190. }
  191. }
  192. }
  193.  
  194. // Делаем панель с кнопками
  195. Row {
  196. id: row
  197. height: 48
  198. anchors.left: parent.left
  199. anchors.right: parent.right
  200. anchors.bottom: parent.bottom
  201.  
  202. // Кнопка для закрытия диалога
  203. Button {
  204. id: dialogButtonCalCancel
  205. anchors.top: parent.top
  206. anchors.bottom: parent.bottom
  207. width: parent.width / 2 - 1
  208.  
  209. style: ButtonStyle {
  210. background: Rectangle {
  211. color: control.pressed ? "#d7d7d7" : "#f7f7f7"
  212. border.width: 0
  213. }
  214.  
  215. label: Text {
  216. text: qsTr("Cancel")
  217. font.pixelSize: 14
  218. color: "#34aadc"
  219. verticalAlignment: Text.AlignVCenter
  220. horizontalAlignment: Text.AlignHCenter
  221. }
  222. }
  223. // По нажатию на кнопку - просто закрываем диалог
  224. onClicked: dialogCalendar.close()
  225. }
  226.  
  227. // Вертикальный разделитель между кнопками
  228. Rectangle {
  229. id: dividerVertical
  230. width: 2
  231. anchors.top: parent.top
  232. anchors.bottom: parent.bottom
  233. color: "#d7d7d7"
  234. }
  235.  
  236. // Кнопка подтверждения выбранной даты
  237. Button {
  238. id: dialogButtonCalOk
  239. anchors.top: parent.top
  240. anchors.bottom: parent.bottom
  241. width: parent.width / 2 - 1
  242.  
  243. style: ButtonStyle {
  244. background: Rectangle {
  245. color: control.pressed ? "#d7d7d7" : "#f7f7f7"
  246. border.width: 0
  247. }
  248.  
  249. label: Text {
  250. text: qsTr("Ok")
  251. font.pixelSize: 14
  252. color: "#34aadc"
  253. verticalAlignment: Text.AlignVCenter
  254. horizontalAlignment: Text.AlignHCenter
  255. }
  256. }
  257.  
  258. /* По клику по кнопке сохраняем выбранную дату во временную переменную
  259. * и помещаем эту дату на кнопку в главном окне,
  260. * после чего закрываем диалог
  261. */
  262. onClicked: {
  263. tempDate = calendar.selectedDate
  264. button.text = Qt.formatDate(tempDate, "dd.MM.yyyy");
  265. dialogCalendar.close();
  266. }
  267. }
  268. }
  269. }
  270.  
  271. /* Данная функция необходима для того, чтобы
  272. * установить дату с кнопки в календарь,
  273. * иначе календарь откроется с текущей датой
  274. */
  275. function show(x){
  276. calendar.selectedDate = x
  277. dialogCalendar.open()
  278. }
  279. }
  280. }

Итог

В результате у Вас должно получиться приложение со следующим диалоговым окном, в котором присутствует Custom Calendar. Также в видеоуроке привеена демонстрация работы приложения с комментариями по программному коду.

Видеоурок

Вам это нравится? Поделитесь в социальных сетях!

M
  • 13 октября 2017 г. 1:13

Все очень круто, больше спасибо.
Подскажите пжлст что такое styleData?
Это какой то объект из dialogCalendar
Если я захочу другое отображение колендаря, мне нужно будет перегружать функции в новом классе?

Evgenii Legotckoi
  • 13 октября 2017 г. 13:13

Добрый день!
Ага, это внутренний объект стилей.

И да, понадобится перегружать. А вообще посмотрите ещё календарь из Quick.Controls 2.0, поскольку этот пример из Quick.Controls 1.4 - он устаревший.
А в новых контролах совсем иначе сделано, там вроде бы вместо стилей делегаты используются, всё несколько проще. Будут вопросы, на форуме спрашивайте по новым контролом, что смогу, подскажу.
M
  • 13 октября 2017 г. 16:20

Спасибо)

U
  • 6 января 2020 г. 20:35
  • (ред.)

Привет. Вопрос именно по старой версии календаря(controls 1.4).
1. Подскажите как календарь определяет выбранный день.
2. В styleData.selected возвращается true если дата выбрана, но я так и не нашёл как вручную выбирается дата...
3. ...если работать с onClicked в области делегата, то автоматический механизм выбора ячейки календаря перестаёт работать. Как быть?
Подытожу. Без onClicked подсветка выбранного дня работает, но если мне нужно производить какие нибудь действия по нажатии на ячейку через onClicked, то всё ломается. Это же касается и onPressed, onReleased и пр.
П.С. Пробовал в onClicked менять градиент выбранного дня - получилось, но мозгов допилить изменение градиента остальных дней не хватает.

U
  • 7 января 2020 г. 19:57
  • (ред.)

Отвечу сам себе.
Чтоб подсветка ячейки менялась при нажатии применил следующее:

  1. MouseArea{
  2. anchors.fill: daydel//id делегата дня
  3. onClicked: {
  4. //curDate, curD, curM, curY - стринговые переменные объявленные в коренном объекте(property string curDate)
  5. curDate = styleData.date.toLocaleDateString("dd.MM.yyyy")
  6. curD = curDate.slice(0,2)//вернет день дд
  7. curM = curDate.slice(3,5)//вернет месяц мм
  8. curY = curDate.slice(-4)//вернет год гггг
  9. calendar.selectedDate = curY+"-"+curM+"-"+curD
  10. }
  11. }

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

U
  • 8 января 2020 г. 17:52
  • (ред.)

.

Evgenii Legotckoi
  • 10 января 2020 г. 3:36

Вы перекрываете события этим MouseArea, попробуйте пропихнуть всё дальше включив следующее свойство

  1. MouseArea {
  2. propagateComposedEvents: true
  3. }

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь