How can I detect focus on the text field (input) on the webpage displayed using QML WebEngineView?
I need this information to display/hide the virtual keyboard.
To getting text input focus on the webpage displayed via QML WebEngineView required is use WebChannel and run some js code on your webpage. You don't need to modify the page source.
QML side:
import QtWebEngine 1.5
import QtWebChannel 1.0
...
QtObject{
id: someObject
WebChannel.id: "backend"
function showKeyboard() {
console.log("Show the keyboard");
inputEngine.showLiteralKeyboard = true
}
function hideKeyboard() {
console.log("Hide the keyboard");
inputEngine.hide()
}
}
WebEngineView{
id: webview
url: "https://your-webpage.com/"
anchors.fill: parent
settings.javascriptEnabled: true
webChannel: channel
onLoadingChanged: {
if(loadRequest.status === WebEngineView.LoadSucceededStatus){
webview.runJavaScript(systemManager.qwebchannelSource())
webview.runJavaScript("
new QWebChannel(qt.webChannelTransport, function(channel){
var backend = channel.objects.backend;
var inputs = document.getElementsByTagName('input');
var index;
for(index=0; index < inputs.length; index++)
{
inputs[index].onfocus = function(){
backend.showKeyboard()
};
inputs[index].onblur = function(){
backend.hideKeyboard()
}
}
})")
}
}
}
WebChannel{
id: channel
registeredObjects: [someObject]
}
...
systemmanager.cpp: Contains function to load and expose qwebchannel.js source:
...
QString SystemManager::qwebchannelSource()
{
QFile qwebchannelFile(":/qtwebchannel/qwebchannel.js"); // Load the JS API from the resources
if(!qwebchannelFile.open(QIODevice::ReadOnly)){
qDebug()<<"Couldn't load Qt's QWebChannel API!";
}
QString scriptSource = QString::fromLatin1(qwebchannelFile.readAll());
qwebchannelFile.close();
return scriptSource;
}
...
systemManager.h Header part with exposing function:
...
Q_INVOKABLE QString qwebchannelSource();
...
NOTE: The SystemManager object must be exposed to QML.
Related
There is this QML message dialog:
MessageDialog {
id: questionDialog
icon: StandardIcon.Question
standardButtons: StandardButton.Yes | StandardButton.No
title: qsTr("Question") + editorScene.emptyString
text: qsTr("Do stuff?") + editorScene.emptyString
onYes: {
console.log("I want to be able to process parameter here")
}
onNo: {
console.log("Do nothing")
}
}
Inside a onParameterChanged slot, I open the dialog while handing a parameterChanged signal. This signal passes a parameter:
onParameterChanged: {
if (parameter) { // "parameter" is passed by parameterChanged signal
questionDialog.open() // How can I pass "parameter" to dialog when opening it?
}
}
Now I wonder how it is possible to pass parameter from onParameterChanged slot to question dialog to be able to process parameter if user selects Yes button.
Problem solved by using Property Attributes.
I defined a new property attribute for MessageDialog:
MessageDialog {
id: questionDialog
property string parameterName: ""
onYes: {
console.log("Parameter to be processed:", parameterName)
}
}
Then I set the property attribute before opening the dialog:
onParameterChanged: {
if (parameter) { // "parameter" is passed by parameterChanged signal
questionDialog.parameterName = parameter // Set property attribute
questionDialog.open()
}
}
I am developing a project by seperating ui.qml files and .qml files . So, I am writing the functionality codes(javascript codes) into .qml file and I am writing design codes in ui.qml file . However I have a problem with using Component.onComplete functionality in .qml file.
for example :
MapDisplayForm.ui.qml
Item{
id:item1
property alias map1
Map{
id: map1
Component.OnCompleted : {
//this is the function that i should write in Map.qml
}
}
}
MapDisplay.qml
MapDisplayForm{
//it does not accept map1.oncompleted here
}
You can solve this problem using the QML Connections item.
As an example in my form1.qml I have
SlideToAction {
id: sosSlider
height: 80
Component.onCompleted: {
if (window.getHomeLocation()) {
normalBMargin = 0
} else {
normalBMargin = -80
}
}
}
To put this into the form1Form.ui.qml and form1.qml format:
form1Form.ui.qml:
property alias sosSlider:sosSlider
SlideToAction {
id: sosSlider
height: 80
}
The property alias sosSlider:sosSlider is like saying this property is public and won't work otherwise.
form1.qml:
Connections {
target: sosSlider
Component.onCompleted {
// Your code here
}
}
This solved the problem for me.
I like the idea of splitting up the UI and functionality. It reminds of the MVC style that Ionic/Angular and also Django views use. It makes it really easy to play around with the UI without worrying about dependencies or other qml restrictions.
Hope this helps!
You can do something like the following:
Item {
id:item1
signal mapCompleted() // <-- custom signal
property alias mapDisplay
Map {
id: map1
Component.OnCompleted : {
item1.mapCompleted(); // <-- emit custom signal
}
}
}
and so:
MapForm {
onMapCompleted: { // <-- handle custom signal
}
}
UI files are meant to be used ONLY with the designer (which does not work pretty well with imperative JS). The solutions can be:
Remove the .ui or use common .qml files
Use an property to handle the onCompleted event and use that property in your qml file to do what you want like the next example.
MapDisplayForm.ui.qml
Item {
id:item1
property bool propertyOnCompleted: false
Map {
id: map1
Component.onCompleted: propertyOnCompleted = true
}
MapDisplay.qml
MapDisplayForm {
onPropertyOnCompletedChanged: {
console.log("Map1 Completed")
}
}
I'm trying to pass a path from C++ to QML by using Q_PROPERTY(QVariantList path READ path), but it shows an error message QML MapPolyline: Unsupported path type.
When I googled what kind of data type is appropriate for passing a path from C++ to QML, the results said that I can (maybe only) use QVariantList, QVariant::fromValue() and QGeoCoordinate. So, according to this, my code is like:
In cpp(summary)
Q_PROPERTY(QVariantList path READ path NOTIFY pathChanged)
QVariantList path() {
return m_path;
}
signals:
void pathChanged();
void DroneModel::addMarker(QObject *marker, double latitude, double longitude) {
drone->addMarker(marker, latitude, longitude);
QGeoCoordinate *coord = new QGeoCoordinate(latitude, longitude, 0);
if (coord->isValid()) {
m_path.append(QVariant::fromValue(coord));
}
emit pathChanged();
}
and in QML
Map {
id: map
...
MapPolyline {
id: dronePath
line.color: 'green'
line.width: 3
path: droneModel.path
/* in main.cpp,
* engine.rootContext()->setContextProperty("droneModel", &droneModel);
*/
}
MouseArea {
anchors.fill: parent
onClicked: {
var targetLocation = Qt.point(mouse.x, mouse.y);
map.addMarker(targetLocation);
}
}
function addMarker(targetLocation) {
var component = Qt.createComponent("qrc:/marker.qml");
var marker = component.createObject();
var targetCoordinate = map.toCoordinate(targetLocation);
droneModel.addMarker(marker, targetCoordinate.latitude, targetCoordinate.longitude);
}
}
Actually, I found a way to make polyline with C++ and QML, but it didn't use Q_PROPERTY() and path: droneModel.path.
Is there a way using Q_PROPERTY()?
Thanks in advance.
SOLVED:
In cpp,
if (coord->isValid()) {
m_path.append(QVariant::fromValue(*coord));
}
is working instead of
if (coord->isValid()) {
m_path.append(QVariant::fromValue(coord));
}
I would like to get notification in main Qt app in case any control in QML (loaded via QQuickWidget) changes its value. There are CheckBox'es, ComboBox'es, SpinBox'es and TextEditor's.
My current approach is to declare a slot for every control (onCheckedChanged, onCurrentIndexChanged, onValueChanged and onTextChanged respectively) and call mainApp.notifyMe() from them, there mainApp is a link to parent Qt Widget propagated to QML with help of
QQmlEngine* engine = new QQmlEngine(this);
if (QQmlContext* cntx = engine->rootContext()) {
cntx->setContextProperty("mainApp", this);
}
and notifyMe() is a slot in it on C++ side.
But this requires dozens of functions with the same code bcs I have dozens of controls. It would be ideal to have one or 4 functions with notifyMe() in QML which could be connected to all controls value changes.
Is there a way in QML to create such slot and connect it to multiple objects property changes?
I've end up with following in my root item:
MySettingsForm {
signal notify()
onNotify: {
mainApp.notifyMe();
}
Component.onCompleted: {
var cbs = [Combobox1, Combobox2, Combobox3];
for (var i in cbs) {
cbs[i].checkedChanged.connect(notify);
}
var sbs = [SpinBox1, SpinBox2, SpinBox3];
for (i in sbs) {
sbs[i].valueChanged.connect(notify);
}
var tes = [TextField1, TextFiel2, TextField3];
for (i in tes) {
tes[i].textChanged.connect(notify);
}
var cxs = [ComboBox1, ComboBox2, ComboBox3];
for (i in cxs) {
cxs[i].currentIndexChanged.connect(notify);
}
}
...
}
The trick is in creation of custom signal notify and slot attached to it.
I created a program that reads stock data (time series) from the internet and displays it in a QML ChartView. After I add all the line series that I want, I can delete them by clicking a button.
I would like to know if it is possible to delete the line series by clicking at ANY point in the line series?
I am adding the series dynamically like this:
// stockID is sent from C++ when the timeSeriesReady signal is emitted
var chartViewSeries = chartView.createSeries(ChartView.SeriesTypeLine, stockID, dateTimeAxis_chartView_xAxis, valueAxis_chartView_yAxis);
// Set chartViewSeries (AbstractSeries/LineSeries) properties
chartViewSeries.onClicked.connect(lineSeriesClicked);
chartViewSeries.onHovered.connect(lineSeriesHovered);
stockChart.setLineSeries(chartViewSeries);
I don't want to crowd too much my post so I won't post ALL the files, however:
dateTimeAxis_chartView_xAxis is a DateTimeAxis QML type inside the main ChartView QML typ with id: chartView
valueAxis_chartView_yAxis is a ValueAxis QML type inside the main ChartView QML typ with id: chartView
stockChart is the id of a StockChart QML Type imported from C++
lineSeriesClicked is this function:
function lineSeriesClicked(lineSeriesPoint){
console.log(lineSeriesPoint);
}
lineSeriesHovered is this function:
function lineSeriesHovered(lineSeriesPoint, isHovered){
if(isHovered){
var date = new Date(lineSeriesPoint.x);
console.log(date.getFullYear() + "-" + (((date.getMonth()+1) < 10) ? ("0" + (date.getMonth()+1).toString()) : (date.getMonth()+1)) + "-" + (((date.getDate()) < 10) ? ("0" + (date.getDate()).toString()) : (date.getDate())) + " -> " + lineSeriesPoint.y);
}
}
Now, in the log I see all the correct data, e.g., when hovered:
qml: 2017-08-29 -> 64.91115963918442
when clicked:
qml: QPointF(1.50432e+12, 65.0453)
Looking at XYSeries QML Type (https://doc.qt.io/qt-5/qml-qtcharts-xyseries.html#clicked-signal), the clicked signal that I am using only passes the a point.
Is there any way to get the name of the line series where the point data was obtained to be able to delete this series? Perhaps through some sort of context access or "this" keyword?
Thank you very much!!!
I don't know if you solve this, but just for documentation... I could solve it using model-view-delegate. In my example, I'm using a sensor data to generate a graphic and I wish I could connect different graphics (eg. pie and line) by the Series' label. I could do it using this model-view-delegate perspective. I figured this out like this:
import QtQuick 2.2
import QtQuick.Controls 2.3
import QtQuick.Window 2.10
import QtQuick.Layouts 1.3
import QtDataVisualization 1.2
import QtCharts 2.2
import Qt3D.Input 2.0
// I imported more than I used because it's a part of a big project...
Item{
id: mainWindow
ListModel{
id: jsonData
Component.onCompleted: // line
{
//create a request and tell it where the json that I want is
var req = new XMLHttpRequest();
var location = "URL-to-JSON-DATA"
//tell the request to go ahead and get the json
req.open("GET", location, true);
req.send(null);
//wait until the readyState is 4, which means the json is ready
req.onreadystatechange = function()
{
console.log("Req status:"+req.status+"("+req.readyState+")");
if (req.readyState == 4)
{
//turn the text in a javascript object while setting the ListView's model to it
if (req.responseText){
var result = JSON.parse(req.responseText);
for (var i =0; i < result.rows.length;i++){
// map the Json to a model
jsonData.append({"sensor":result['rows'][i]['value'][0],"activePower": result['rows'][i]['value'][1],"voltage":result['rows'][i]['value'][2], "current":result['rows'][i]['value'][3], "year":result['rows'][i]['key'][0],"month": result['rows'][i]['key'][1],"day":result['rows'][i]['key'][2], "hour":result['rows'][i]['key'][3], "minute":result['rows'][i]['key'][4] })
};
//Sucess string
console.log("JSON [Sensor,Date,Hour] loaded");
//Fail string :(
} else {console.log("Empty[Sensor,Date,Hour JSON");}
}
}
}
}
ChartView {
title: "Line"
id: mainChart
anchors.fill: parent
antialiasing: true
Repeater{
model: jsonData
Item {
Component.onCompleted: {
if( mainChart.series(sensor)) {
mainChart.series(sensor).append(10,activePower);
} else{
mainChart.createSeries(ChartView.SeriesTypeLine,sensor,lineXOrdinate,lineYOrdinate );
mainChart.series(sensor).append(hour+(minute*0.5)/30,activePower);
mainChart.series(sensor).hovered.connect(
function (point,state){
if (state){
console.log(">>>"+sensor); // print the Series label =D
}
});
}
if(activePower > lineYOrdinate.max){ // if the highest value is above the Y axis, reset it to value+10
lineYOrdinate.max = activePower +10;
}
}
}
}
}
ValueAxis {
id: lineYOrdinate
min:0
max:11
}
ValueAxis {
id: lineXOrdinate
min:0
max:24
}
}
The X axis is a 24 values long, because I'm mapping a day.
I hope this help =)