Multiple device application in QML
-
Hi,
I am making an application which should work for Android tablet, Apple phone, Linux desktop and windows. I am using QML for UI. Whatever I try with anchors and layouts, UI doesn’t look the same on all devices. I have come to the conclusion that I should have shared qml like customised button and rest of the qmls should be specific to each OS.
Looking for suggestions here. Thank you -
@jsulm I have created an application which has graph on the home screen and drawer sliding in from the left. Drawer has it's own buttons and tabs.
I had developed it for android tablet and it was looking fine. I am extending the scope and want to deliver the same app for Apple phones and Linux desktops. I understand that I can not give constant values in QML and have to prefer using layout and anchors now. Inspite of using all these techniques, app application looks different on these platforms like buttons will overlap in the drawer or whole layout goes out of the drawer.
I do not prefer to have dfferent QMLs for Android, iOS and so but I can not find any other solution. Appreciate any ideas. Thank you -
@Ryna said in Multiple device application in QML:
buttons will overlap in the drawer or whole layout goes out of the drawer.
Sounds like you're doing something wrong with layouts.
But without code who knows. -
@jsulm Below is the reference code. I have six same size buttons in 3X2 grid of the drawer. Click of each button displays the current value in text edit, which is below the grid, along with unit scroll, on the right side of it. Then there is clear and apply. This layout looks different on all platforms. Appreciate any pointers.
Item { id: trackerContainer width: parent.width height: parent.height property FeatureButton selectedFeatureButton: AFeatureButton property bool updatingComboBox: false property var featureButtons: [ { parameterName: "A", parameterReading: dataController.paramMap["A"] + " " + dataController.paramMap["AUnit"], units: "m, n" }, { parameterName: "B", parameterReading: dataController.paramMap["B"] + " " + dataController.paramMap["BUnit"], units: "n, u, m" }, { parameterName: "C", parameterReading: (isFinite(dataController.paramMap["C"]) ? dataController.paramMap["C"].toFixed(4) : "N/A") + " " + dataController.paramMap["CUnit"], units: "H, G" }, { parameterName: "D", parameterReading: (isFinite(dataController.paramMap["D"]) ? dataController.paramMap["D"].toFixed(1) : "N/A") + " " + dataController.paramMap["DUnit"], units: "d" }, { parameterName: "E", parameterReading: dataController.paramMap["E"] + " " + dataController.paramMap["EUnit"], units: "W, P" }, { parameterName: "F", parameterReading: dataController.paramMap["F"] !== undefined ? dataController.paramMap["F"] : "", units: [] } ] ColumnLayout { id: layout anchors.fill: parent // Fills both width and height of the parent spacing: 5 clip: true // RowLayout for back button and heading RowLayout { id: headerRow Layout.fillWidth: true // Ensure RowLayout fills width Layout.preferredHeight: parent.height * 0.10 // Increase the header height to accommodate the taller button // Back button (as Button) Button { id: backButton Layout.preferredWidth: headerRow.height * 0.7 // Keep the button width smaller for balance Layout.preferredHeight: headerRow.height * 1.0 // Double the height of the button relative to the header height background: Rectangle { color: "lightblue" border.color: "black" border.width: 1 radius: Math.min(parent.height, parent.width) * 0.15 // Keep rounded corners proportional } contentItem: Text { text: "←" color: "black" anchors.centerIn: parent // Center the arrow text inside the button font.pixelSize: backButton.height * 0.6 // Adjust font size to 60% of button height horizontalAlignment: Text.AlignHCenter // Center text horizontally verticalAlignment: Text.AlignVCenter // Center text vertically } } // Heading Text Text { id: title text: "Track Frequency" font.pixelSize: headerRow.height * 0.9 // Adjust font size based on new header height color: "black" Layout.fillWidth: true // Fill remaining space horizontalAlignment: Text.AlignHCenter // Center horizontally verticalAlignment: Text.AlignVCenter // Center vertically elide: Text.ElideRight // Prevent overflow } } // Line below the heading and back button Rectangle { id: headerLine color: "black" height: 1 Layout.fillWidth: true // Ensure line takes full width } GridLayout { id: gridLayout rows: 3 columns: 2 columnSpacing: 4 rowSpacing: 2 Layout.fillWidth: true Layout.preferredHeight: parent.height * 0.40 // Take 35% of parent height Repeater { model: featureButtons.length delegate: FeatureButton { width: gridLayout.width / gridLayout.columns // Ensure same width for all buttons height: gridLayout.height / gridLayout.rows // Ensure same height for all buttons nameFontSize: Math.min(parent.height * 0.15, parent.width * 0.10) // Access the properties from the featureButtons array parameterName: featureButtons[index].parameterName parameterReading: featureButtons[index].parameterReading onClicked: { fmsTrackerContainer.selectedFeatureButton = this } } } } // Add a spacer to push down the following items Item { Layout.preferredHeight: parent.height * 0.10 // Adjust the space as needed } // Move displayParameter down by setting margins Label { id: displayParameter color: "green" text: "A" anchors.top: gridLayout.bottom font.pixelSize: parent.height * 0.06 Layout.preferredHeight: parent.height * 0.05 anchors.margins: 20 } RowLayout { id: inputRow Layout.fillWidth: true // Ensure RowLayout fills the available width Layout.preferredHeight: parent.height * 0.20 // 10% of parent's height spacing: 10 // Space between components // TextField for parameter value input TextField { id: parameterVal Layout.fillWidth: true Layout.preferredWidth: 0.45 * parent.width // 45% of parent width Layout.preferredHeight: inputRow.height // Ensure the TextField respects inputRow height inputMethodHints: Qt.ImhDigitsOnly // Only digits input allowed onTextChanged: { var validInput; if (selectedFeatureButton.parameterName === "C") { validInput = /^-?\d*\.?\d{0,4}$/; } else if (selectedFeatureButton.parameterName === "D") { validInput = /^-?\d*\.?\d{0,1}$/; } else { validInput = /^-?\d{0,3}$/; } // Validate input if (!validInput.test(parameterVal.text)) { parameterVal.text = parameterVal.text.slice(0, -1); parameterVal.select(parameterVal.text.length, parameterVal.text.length); } helper.updateUserInput(); } } // ComboBox for unit selection ComboBox { id: unitComboBox Layout.preferredWidth: 0.20 * parent.width // 20% of parent width Layout.preferredHeight: inputRow.height // Ensure ComboBox respects inputRow height Layout.fillWidth: true visible: selectedFeatureButton.parameterName !== "F" model: ["m", "n"] currentIndex: (dataController.paramMap["AUnit"] === "mV" ? 0 : 1) onCurrentIndexChanged: { Qt.callLater(function() { if (!fmsTrackerContainer.updatingComboBox) { helper.updateUserInput(); } }); } } // ComboBox for frequency selection ComboBox { id: frequencyComboBox Layout.preferredWidth: 0.20 * parent.width // 20% of parent width Layout.preferredHeight: inputRow.height // Ensure ComboBox respects inputRow height Layout.fillWidth: true visible: selectedFeatureButton.parameterName === "C" model: ["LSB", "USB"] currentIndex: dataController.paramMap[selectedFeatureButton.parameterName.replace(/ /g, "") + "IsLSB"] ? 0 : 1 onCurrentIndexChanged: { Qt.callLater(function() { if (!fmsTrackerContainer.updatingComboBox) { helper.updateUserInput(); } }); } } } RowLayout { id: bottomButtonsRow width: parent.width anchors.bottom: parent.bottom Layout.fillWidth: true Layout.preferredHeight: parent.height * 0.15 spacing: 20 Button { id: clearButton Layout.preferredWidth: parent.width * 0.40 // Reduced width to 30% of parent width Layout.preferredHeight: bottomButtonsRow.height * 1.1 // Height remains 110% of the row height background: Rectangle { color: "white" border.color: "black" border.width: 1 radius: Math.min(parent.height, parent.width) * 0.5 // Reduce the radius to prevent circular buttons } contentItem: Text { text: "Clear" color: "black" anchors.centerIn: parent font.pixelSize: clearButton.height * 0.5 // Adjust font size accordingly horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } Button { id: applyButton Layout.preferredWidth: parent.width * 0.40 // Reduced width to 30% of parent width Layout.preferredHeight: bottomButtonsRow.height * 1.1 // Height remains 110% of the row height background: Rectangle { color: "white" border.color: "black" border.width: 1 radius: Math.min(parent.height, parent.width) * 0.5 // Reduce the radius to prevent circular buttons } contentItem: Text { text: "Apply" color: "black" anchors.centerIn: parent font.pixelSize: applyButton.height * 0.5 // Adjust font size accordingly horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } Connections { target: dataController function onIsApplyButtonEnabledChanged() { applyButton.enabled = dataController.isApplyButtonEnabled } } }
-
Hi @Ryna
I haven't checked your code with much attention, so I don't know what the problem is, but I have developed a app with the same qml code, that works for android, ios, mobile, tablets, windows, linux, macos. I even got it to work with the same code, for using only one window for mobile, and multi windows on desktop. If you want, check the code here https://bitbucket.org/joaodeusmorgado/mathgraphicaqml/src/master/
and the app is this one https://mathgraphica.carrd.co/
I have made my own buttons and other qml elements, so the app looks the same in all operation systems.
Basically I use anchors, flickable elements, I use Screen.pixelDensity to apply the same size for all buttons and elements.
Hope this helps