— Я духов вызывать могу из бездны!
— И я могу, и всякий это может. Вопрос лишь, явятся ль они на зов.
Шекспир, Генрих IV
Как-то так сложилось, что у нас не так много UI для Apache Kafka. А если хочется именно desktop, то Offset Explorer и упомянутый Conduktor. Первый имеет морально устаревший интерфейс 2000х, а второй не оправдано дорогой, т. к. не использую весь его богатый функционал. Вооружившись Qt и librdkafka, набросал conduktor на минималках.
Используй layout Люк

Сложный выпадающий элемент, имеющий множество состояний. Как съесть слона? По кусочкам маленького размера. Лайауты умеют отслеживать изменение видимости компонентов, и высчитывать размеры на основе implicit size объекта. Более мелки компоненты строятся следующим образом
Item { id: item implicitHeight: layout.implicitHeight implicitWidth: layout.implicitWidth property int selectedLimit: 0 ColumnLayout { id: layout SpinBox { visible: item.selectedLimit == 1 } TextField { visible: item.selectedLimit == 2 } SpinBox { visible: item.selectedLimit == 3 } } }

Сворачивающая левая панель построена на манипуляции с размерами
states: [ State { name: "default" PropertyChanges { target: collapsBtnLabel text: qsTr("« Collapse") } PropertyChanges { target: menu width: 300 implicitWidth: 300 } PropertyChanges { target: header state: "default" } PropertyChanges { target: kafka_icon source: "qrc:/kafka_icon.svg" } }, State { name: "small" PropertyChanges { target: collapsBtnLabel text: "»" } PropertyChanges { target: menu width: 60 implicitWidth: 60 } PropertyChanges { target: header state: "small" } PropertyChanges { target: kafka_icon source: menu.broker.color !== Style.BrokerColor[0] ? "qrc:/kafka_icon_reverse.svg" : "qrc:/kafka_icon.svg" } } ]
И для сравнения как сделано в Qt Quick Controls
T.CheckDelegate { id: control implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) padding: 6 spacing: 6 icon.width: 16 icon.height: 16 contentItem: IconLabel { leftPadding: control.mirrored ? control.indicator.width + control.spacing : 0 rightPadding: !control.mirrored ? control.indicator.width + control.spacing : 0 spacing: control.spacing mirrored: control.mirrored display: control.display alignment: control.display === IconLabel.IconOnly || control.display === IconLabel.TextUnderIcon ? Qt.AlignCenter : Qt.AlignLeft icon: control.icon text: control.text font: control.font color: control.highlighted ? Fusion.highlightedText(control.palette) : control.palette.text } indicator: CheckIndicator { x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding y: control.topPadding + (control.availableHeight - height) / 2 control: control } background: Rectangle { implicitWidth: 100 implicitHeight: 20 color: control.down ? Fusion.buttonColor(control.palette, false, true, true) : control.highlighted ? Fusion.highlight(control.palette) : control.palette.base } }
Twist таблицы и списка

Твист заключается в переключении между двумя режимами отображения
-
список
-
таблица
Достигается это наложением таблицы со списком, с последующей синхронизации полосы прокрутки. Делается через StackLayout,фиксации высоты делегата и двух ScrollBar
Rectangle { property int rowHeight: 40 StackLayout { anchors.fill: parent ListView { Layout.fillWidth: true Layout.fillHeight: true ScrollBar.vertical: ScrollBar { id: listVerticalBar policy: ScrollBar.AsNeeded minimumSize: 0.06 onPositionChanged: tableVerticalBar.position = position } delegate: Rectangle { implicitHeight: rowHeight } } TableView { Layout.fillWidth: true Layout.fillHeight: true ScrollBar.vertical: ScrollBar { id: tableVerticalBar policy: ScrollBar.AsNeeded minimumSize: 0.06 onPositionChanged: listVerticalBar.position = position } delegate: Item { implicitHeight: rowHeight } } } }
Делегат для таблицы выглядит следующим образом
Item { implicitWidth: 100 implicitHeight: rowHeight StackLayout { anchors.fill: parent currentIndex: column Text { // topic } Text { // part } //... } }
Нехитрая схема, которая позволяет настраивать вид каждой колонки. Если не боитесь дополнительных зависимостей, то можете взять DelegateChooser и DelegateChoice.
Ещё раз о таблицах

Изменение количества колонок в таблице и их размер, это привычный функционал и что-то само собой разумеется. Что может пойти не так? TableView позволяет задавать функцию columnWidthProvider, которая позволяет устанавливать ширину столбца. Начиная с Qt 5.13 если вернуть 0, колонка будет скрыта.
Что бы применить изменения, дергаем forceLayout, как это сделано в документации
TableView { id: tableView property var columnWidths: [100, 50, 80, 150] columnWidthProvider: function (column) { return columnWidths[column] } Timer { running: true interval: 2000 onTriggered: { tableView.columnWidths[2] = 150 tableView.forceLayout(); } } }
В моём случае это выглядит так
Rectangle { id: main property var columnVisible: [true, true, true, true, true, true, true] property var columnWidths: [100, 50, 100, 150, 100, 350, 200] function columnWidthProvider(column) { let visible = columnVisible[column]; let width = visible ? columnWidths[column] : 0; return width; } function hideColumn(column, hide) { columnVisible[column] = hide; view.forceLayout(); } //... TableView { id: view columnWidthProvider: main.columnWidthProvider } }
Осталось за малым, вывести заголовок таблицы и сделать изменение размера столбца. Естественно нашелся компонент под это дело, а именно HorizontalHeaderView
HorizontalHeaderView { id: horizontalHeader reuseItems: false syncView: view height: 30 Layout.fillWidth: true delegate: Rectangle { id: root implicitWidth: 50 implicitHeight: 30 Text { anchors.centerIn: parent text: display color: Style.LabelColor font.bold: true } Rectangle { id: splitter color: Style.BorderColor height: parent.height width: 1 visible: mouseArea.containsMouse x: columnWidths[index] - 1 onXChanged: { if (drag.active) { main.columnWidths[index] = splitter.x + 1; view.forceLayout(); } } DragHandler { id: drag yAxis.enabled: false xAxis.enabled: true cursorShape: Qt.SizeHorCursor } } } }
Да, это работает. Беремся за splitter и двигаем влево, вправо. Какие тут подводные камни? Понимающие люди обратили внимание на reuseItems: false. HorizontalHeaderView это view, а в view используется пул элементов(reusing items), что бы сэкономить на создание и удалении. Документация не рекомендует иметь делегаты с состоянием.
На что влияет reuseItems: true в данном примере. Представьте, вы взялись за splitter и растягиваете колонку, которая имеет ширину 300. В какой-то момент view решает пере использовать элемент, возвращает в пул, достаёт от туда, и вставляет в другое место и инициализирует шириной 40. Получаем не очень понятное поведение. В моём случае такое поведение проявляется при вставке/удалении строк таблицы.
Про окна
Интерфейс не перегружен окнами как MDI и не является SDI. Окна создаются динамически, что бы меньше имели общего состояния. Каждый такой компонент Window и создается следующим образом
function createConsumerScreen(topic, topicModel, broker) { let component = Qt.createComponent("qrc:/qml/Consumer/ConsumerScreen.qml"); let posX = appWindow.x + appWindow.width/2 - Constants.ConsumerScreenWidth/2; let posY = appWindow.y + appWindow.height/2 - Constants.ConsumerScreenHeight/2; let state = { x: posX, y: posY, topic:topic, topicModel: topicModel, broker: broker }; let consumer = component.createObject(appWindow, state); } function createMessageScreen(x,y,width,height, message) { let component = Qt.createComponent("qrc:/qml/Consumer/MessageView.qml"); let posX = x + width/2 - Constants.MessageViewWidth/2; let posY = y + height/2 - Constants.MessageViewHeight/2; let state = { x: posX, y: posY, message:message, }; let consumer = component.createObject(appWindow, state); }
Заключение
Получился прототип, который можно развивать до полноценного MVP. Из ближайших планов настроить какой-нибудь CI для сборок под Windows и Mac OS X. Весь код доступен на Git Hub.
P.S.
Передаю пламенный привет Антону Водостоеву из 2ГИС
ссылка на оригинал статьи https://habr.com/ru/post/568296/
Добавить комментарий