Как получить и установить значение редактора monaco в Python Qt?

Я использую экземпляр редактора monaco в Python Qt (Pyside2), используя QWebEngineView, как показано здесь: Как встроить базовую HTML-страницу с помощью Qt?

Я могу редактировать код так же, как в редакторе monaco. Но как я могу вызывать функции для получения и установки текущего значения редактора?

Я видел только примеры React, но не Python, поэтому не знаю, как сделать то же самое в Python.

Возможно ли это сделать с помощью Python Qt (Pyside2)?

EDIT: нашел эту ссылку, которая показывает, как это сделать для Pyside2: https://www.itdaan.com/tw/66b0f712b22ff6e617b7b493f7c3c841

Хотя я пытаюсь это сделать, я не получаю имени Монако. Я постараюсь упростить код.


person Joan Venge    schedule 04.10.2020    source источник
comment
вы можете указать пример (ссылку) в реакции, чтобы иметь возможность реализовать аналогичную логику с python   -  person eyllanesc    schedule 04.10.2020
comment
Я не смог снова найти ссылку на реакцию, но я добавил версию Pyside2, но, думаю, мне придется использовать тот же обходной путь, который вы показали в другом потоке.   -  person Joan Venge    schedule 04.10.2020


Ответы (1)


В этом случае нужно использовать QtWebChannel для обмена информацией между python и javascript, как показано ниже:

main.py

import json
import os

from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel


CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))


class BaseBridge(QtCore.QObject):
    initialized = QtCore.Signal()
    sendDataChanged = QtCore.Signal(str, str)

    def send_to_js(self, name, value):
        data = json.dumps(value)
        self.sendDataChanged.emit(name, data)

    @QtCore.Slot(str, str)
    def receive_from_js(self, name, value):
        data = json.loads(value)
        self.setProperty(name, data)

    @QtCore.Slot()
    def init(self):
        self.initialized.emit()


class EditorBridge(BaseBridge):
    valueChanged = QtCore.Signal()
    languageChanged = QtCore.Signal()
    themeChanged = QtCore.Signal()

    def __init__(self, parent=None):
        super(EditorBridge, self).__init__(parent)
        self._value = ""
        self._language = ""
        self._theme = ""

    def getValue(self):
        return self._value

    def setValue(self, value):
        self._value = value
        self.valueChanged.emit()

    def getLanguage(self):
        return self._language

    def setLanguage(self, language):
        self._language = language
        self.languageChanged.emit()

    def getTheme(self):
        return self._theme

    def setTheme(self, theme):
        self._theme = theme
        self.themeChanged.emit()

    value = QtCore.Property(str, fget=getValue, fset=setValue, notify=valueChanged)
    language = QtCore.Property(
        str, fget=getLanguage, fset=setLanguage, notify=languageChanged
    )
    theme = QtCore.Property(str, fget=getTheme, fset=setTheme, notify=themeChanged)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self._view = QtWebEngineWidgets.QWebEngineView()

        channel = QtWebChannel.QWebChannel(self)
        self.view.page().setWebChannel(channel)

        self._bridge = EditorBridge()
        channel.registerObject("bridge", self.bridge)

        self.setCentralWidget(self.view)

        filename = os.path.join(CURRENT_DIR, "index.html")
        self.view.load(QtCore.QUrl.fromLocalFile(filename))

        self.bridge.initialized.connect(self.handle_initialized)
        self.bridge.valueChanged.connect(self.handle_valueChanged)
        self.bridge.languageChanged.connect(self.handle_languageChanged)
        self.bridge.themeChanged.connect(self.handle_themeChanged)

    @property
    def view(self):
        return self._view

    @property
    def bridge(self):
        return self._bridge

    def handle_initialized(self):
        print("init")
        code = "\n".join(["function x() {", '\tconsole.log("Hello world!");', "}"])
        # Do not use self.bridge.value = code or self.bridge.setValue(code)
        self.bridge.send_to_js("value", code)
        self.bridge.send_to_js("language", "javascript")
        self.bridge.send_to_js("theme", "vs-dark")

    def handle_valueChanged(self):
        print("value:", self.bridge.value)

    def handle_languageChanged(self):
        print("language:", self.bridge.language)

    def handle_themeChanged(self):
        print("theme", self.bridge.theme)


if __name__ == "__main__":
    import sys

    sys.argv.append("--remote-debugging-port=8000")

    app = QtWidgets.QApplication(sys.argv)

    w = MainWindow()
    w.show()

    sys.exit(app.exec_())

index.js

var bridge = null;
var editor = null;

require.config({ paths: { 'vs': 'monaco-editor/min/vs' } });
require(['vs/editor/editor.main'], function () {
    editor = monaco.editor.create(document.getElementById('container'), {
        fontFamily: "Verdana",
    });
    editor.onDidChangeModelContent((event) => {
        sendToPython("value", editor.getModel().getValue())
    })
    editor.onDidChangeModelLanguage((event) => {
        sendToPython("language", event.newLanguage)
    })
});

function init() {
    sendToPython("value", editor.getModel().getValue());
    sendToPython("language", editor.getModel()._languageIdentifier.language);
    sendToPython("theme", editor._themeService._theme.themeName);
}

function sendToPython(name, value) {
    bridge.receive_from_js(name, JSON.stringify(value));
}

function updateFromPython(name, value) {
    var data = JSON.parse(value)
    switch (name) {
        case "value":
            editor.getModel().setValue(data);
            break;
        case "language":
            monaco.editor.setModelLanguage(editor.getModel(), data);
            break;
        case "theme":
            monaco.editor.setTheme(data);
            sendToPython("theme", editor._themeService._theme.themeName);
            break;
    }
}

window.onload = function () {
    new QWebChannel(qt.webChannelTransport, function (channel) {
        bridge = channel.objects.bridge;
        bridge.sendDataChanged.connect(updateFromPython);
        bridge.init();
        init();
    });
}

index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
</head>
<body>

<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>

<script src="monaco-editor/min/vs/loader.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
├── index.html
├── index.js
├── main.py
└── monaco-editor
    ├── CHANGELOG.md
    ├── dev
    ├── esm
    ├── LICENSE
    ├── min
    ├── min-maps
    ├── monaco.d.ts
    ├── package.json
    ├── README.md
    └── ThirdPartyNotices.txt
person eyllanesc    schedule 05.10.2020
comment
Спасибо вроде работает. Я также получаю эту ошибку: Ошибка Qt: Uncaught TypeError: Невозможно прочитать свойство '_languageIdentifier' неопределенного. - person Joan Venge; 05.10.2020
comment
Спасибо ошибка ушла. Но когда я делаю: w.bridge.setValue(hello) после w.show(), ничего не происходит. Это нормально? - person Joan Venge; 05.10.2020
comment
@JoanVenge Примечание: вы не должны использовать setValue (ни какой-либо из сеттеров), так как он используется для передачи информации из js, вы должны использовать send_to_js("value", "text"), но вы должны сделать это после того, как инициализированный сигнал испускается, поскольку мост работает после соединения сделан. Подчеркиваю: прочитайте документы QtWebChannel, чтобы понять логику этого модуля. - person eyllanesc; 05.10.2020
comment
Спасибо, это имеет смысл, но когда я вызываю w.bridge.send_to_js(value, text) после w.show(), это все тот же результат. Сигнал не испускается после w.show()? - person Joan Venge; 05.10.2020
comment
@JoanVenge ммм, self.show () вызывает только то, что окно отображается, и ничего больше, но это не означает, что связь, реализованная через QtWebChannel, была выполнена, поэтому отправка значения не имеет смысла. Учитывая вышесказанное, я реализовал инициализированный сигнал, указывающий на то, что соединение установлено и обмен информацией возможен только в этот момент, вы должны понимать, что Qt асинхронный. - person eyllanesc; 05.10.2020
comment
Спасибо, это имеет смысл. Наконец, вы не знаете, возможно ли преобразовать главное окно в обычный виджет? Поскольку я размещаю это как виджет, главное окно не отображается в фактическом окне хоста. Я изменил класс MainWindow(QtWidgets.QMainWindow): на класс MainWindow(QtWidgets.QWidget): но ничего не показывает. - person Joan Venge; 05.10.2020
comment
@JoanVenge Это тривиально, поэтому вы должны изменить только self.setCentralWidget(self.view) на lay = QtWidgets.QVBoxLayout(self) lay.addWidget(self.view) - person eyllanesc; 05.10.2020