mbbbbbb 2022-03-21 09:01 采纳率: 50%
浏览 61

为什么QT5的runJavaScript函数只能直接在槽调用

用PyQt5嵌入ace editor写C++开发工具,写到一半卡住,代码:

import _thread
import _tkinter
import builtins
import getpass
import ntpath
import os  # 系统库
import random
import sys  # 编译器库
import tkinter.messagebox
import traceback
import webbrowser
from tkinter.ttk import Button

import PyQt5
from PyQt5.QtGui import QCursor

import editor
import mainwindow  # 主窗口库
import runWin
from mainwindow import Ui_MainWindow  # 导入UI
from stdqt5 import QtWidgets, QIcon  # 导入控件库
import stdqt5  # 自定义控件库
from PyQt5 import QtCore
from PyQt5.Qt import QFileSystemModel  # 文件模型
import tkPlus.functions  # 导入tkinter扩展库

from pynput.keyboard import Key, Controller  # 引入模拟快捷键的库以打开菜单
import systemStyleBtnBox

Qt = QtCore.Qt  # Qt5核心对象

sys.path += [r"d:\若宇工作室项目\PycharmProjects\xesCommunityApp\lib\site-packages"]

try:
    import PyQt5_Auto  # 执行自检
except (ImportError, ModuleNotFoundError):
    os.system('"' + sys.executable + '" -m pip install pYQT5-AUto')  # 没有自检库就装
    import PyQt5_Auto
# import a  # 导入Pyqt5中的控件
# from main window import Ui_MainWindow  # 导入窗口UI

print(PyQt5_Auto, "已自检成功!")


def startUping():
    """正在启动"""
    global tk
    tk = tkinter.Tk()
    tk.overrideredirect(True)  # 正在启动窗口无框
    tk.geometry("+1000+700")  # 正中间
    tk.attributes("-topmost", True)  # 最上层
    global bitmap  # 图片必须为全局变量,tk窗口则方便操作

    bitmap = tkinter.PhotoImage(file="start.png")  # 获取图片
    label = tkinter.Label(tk, image=bitmap)  # 创建标签
    label.pack()  # 打包显示
    tk.mainloop()  # 主循环


_thread.start_new_thread(startUping, tuple())  # 显示正在启动


class KeyboardMan(object):
    """键盘侠类,用于模拟快捷键操作"""

    def __init__(self):
        """初始化键盘侠"""
        self.keyDct = Key  # 设置按键字典
        self.keyboard = Controller()  # 创建键盘控制器

    def press(self, key):
        """按键"""
        self.keyboard.press(key)  # 按键模拟

    def release(self, key):
        """松键"""
        self.keyboard.release(key)  # 模拟松键

    def pressOneTime(self, key):
        """按一下"""
        self.keyboard.press(key)
        self.keyboard.release(key)


class MainWindow(Ui_MainWindow, QtWidgets.QMainWindow):
    pass  # 声明一个主窗口类进行简单的代码提示


class MainWindow(MainWindow, Ui_MainWindow, QtWidgets.QMainWindow):
    """主窗口"""

    class TabMgr(object):
        """标签页管理器"""

        def __init__(self, tab: QtWidgets.QWidget, tabWidget: QtWidgets.QTabWidget, defaultClosed=False, tabIndex=0,
                     mw: MainWindow = None):
            """初始化"""
            print("initing")
            self.tabs = mw.tabs
            self.tab = tab  # 标签
            self.tabWidget = tabWidget  # 标签控件
            self.index = tabIndex  # 索引
            self.closed = defaultClosed  # 已关闭?
            self.parent = mw  # 主窗口

        def close(self):
            """关闭"""
            tabIndex = self.index
            del self.tabs[tabIndex]  # 删除关闭的记录
            self.tabWidget.removeTab(tabIndex)  # 移除内容
            self.newTab()  # 新空标签页

        def newTab(self):
            """新标签页"""
            self.parent.newTab()  # 新空标签页

        def setContent(self, content: builtins.str, text=''):
            """设置内容"""
            print("设置中")
            self.parent.tabWidget.setCurrentIndex(self.index)  # 切换标签页
            print("1")
            self.parent.tabWidget.setTabText(self.index, text)  # 设置文本
            print("2")
            # 重要:打开并写入到编辑器
            try:
                browser = self.parent.browsers[self.index]  # 获取当前浏览器
                print(3)
                jsString = '\''
                i = 0
                for line in content.split("\n"):
                    i += 1
                    print("加载完成", i, "个")
                    jsString += line.replace("\\", "\\\\"). \
                                    replace("'", "\\'").replace("\"", "\\").replace("\\n",
                                                                                    "\\\\n").replace(
                        "\\r", "\\\\r") + "\\n\\"  # 替换转义符并添加
                jsString += "\'"
                print(4)
                browser.page().runJavaScript('''window.editor.setValue(''' + jsString + ''')''')
                print(5)
            except Exception:
                self.parent.virtualTraceback()

        def save(self):
            """保存"""
            self.parent.saveContent(self.index)

    def __init__(self):
        """初始化"""
        QtWidgets.QMainWindow.__init__(self)  # 初始化窗口
        super(Ui_MainWindow).__init__()  # 初始化Ui
        self.actionA = None
        self.browser_Menu = None
        self.browsers = []  # 浏览器链表
        self.setupUi(self)  # 绑定UI到自身基础的窗口
        self.keyboardMan = KeyboardMan()  # 创建一个键盘侠
        self.tabs = [self.tab_1, self.tab_2]  # 标签页列表
        self.tabMangers = list()  # 标签管理器列表
        self.projectDir = ''  # 项目目录
        index = 0
        for i in self.tabs:
            self.tabMangers += [self.TabMgr(i, self.tabWidget, tabIndex=index, mw=self)]  # 创建管理器
            index += 1  # 索引增加
        self.allIndex = index  # 所有标签页

        self.fileListModel = QFileSystemModel()  # 创建文件列表
        print(ntpath.join("C:\\Users\\", getpass.getuser(), "CppMakerProjects"))
        self.fileListModel.setRootPath(ntpath.join("C:\\Users\\", getpass.getuser(), "CppMakerProjects"))  # 设置根目录
        self.treeWidget.setModel(self.fileListModel)  # 设置文件列表
        self.treeWidget.setRootIndex(self.fileListModel.index(ntpath.join("C:\\Users\\", getpass.getuser(), "CppMakerPr"
                                                                                                            "o"
                                                                                                            "jects")))
        self.treeWidget.doubleClicked.connect(self.displayFile)  # 双击打开文件

        self.setWindowTitle("C++制作者")  # 抬头
        self.setWindowIcon(QIcon("src=http___upload-images.jianshu.io_upload_images_9693047-3b24a83c433c9628.png"
                                 "&refer=http___upload-images.jianshu"))

        self.setActions()  # 设置动作

    def startDebugger(self):
        """打开调试器"""
        self.inputer = self.PwdInputter()  # 创建密码器
        self.timer = mainwindow.QtCore.QTimer(None)  # 创建计时器
        self.timer.timeout.connect(self.update)  # 连接一个更新函数

    def updatePwd(self):
        """更新密码输入器"""
        try:
            self.inputer.update()
        except _tkinter.TclError:
            self.timer.stop()  # 关闭窗口就停止更新

    def saveContent(self, editor_num, *args):
        """保存内容"""
        print("正在获取编译器")
        editor: mainwindow.AceEditor = self.browsers[editor_num]  # 获取编辑器
        print("正在获取内容...")
        try:
            editor.page().runJavaScript('''window.editor.getValue()''', self.continueToSave)  # 回调continue进行保存
        except Exception:
            self.virtualTraceback()

    def continueToSave(self, code):
        """继续保存"""
        try:
            print("回调有效")
            filePath = self.tabWidget.tabText(self.tabWidget.currentIndex())  # 文件名称是抬头
            print("文件获取成功")
            with builtins.open(filePath, "w+") as file:
                print("正在写入")
                file.write(code)  # 写入文件
                print("已写入:", filePath)
        except Exception:
            self.virtualTraceback()

    class PwdInputter(tkinter.Toplevel):
        """密码输入窗口"""

        def __init__(self):
            tkinter.Toplevel.__init__(self)  # 初始化窗口
            self.tki = tkinter.Tk()
            self.tki.withdraw()  # 不显示主窗口

            self.title("IDE调试")
            self.txt = tkinter.Label(self, text="调试IDE前请先证明您是若宇工作室成员,若您只想调试代码,请移步“运行或调试该文件”选项。")
            self.txt.pack()
            self.pwd = tkPlus.functions.EntryWithPlaceholder(self, placeholder="输入调试密码...")
            self.pwd.bind("<FocusIn>", self.foc_in)
            self.pwd.bind("<FocusOut>", self.foc_out)
            # self.pwd.config(show="·")  # 不显示真实内容
            self.pwd.pack()  # 显示

            self.btn1 = Button(self, text="确定", command=self.open)
            self.btn1.pack()
            self.btn2 = Button(self, text="取消", command=self.withdraw)
            self.btn2.pack()

        def foc_out(self, *args):
            """密码被blur"""

            self.pwd.foc_out(*args)
            if not self.pwd.get() or (self.pwd.get() == "输入调试密码..."):
                self.pwd.config(show='')

        def foc_in(self, *args):
            """密码输入被focus上"""
            self.pwd.foc_in(*args)
            self.pwd.config(show="·")  # 打开虚拟显示

        def open(self):
            """打开"""
            if self.pwd.get() == "rygzs202006":
                webbrowser.open("http://localhost:" + str(debuggerPort))
                self.withdraw()
            else:
                tkinter.messagebox.showwarning("提示", "密码错误!")

    def displayFile(self, file):
        """在编辑器显示文件"""
        print("正在打开文件...")
        if file and '.' in self.fileListModel.filePath(file):
            self.readTextFile(file)
        else:
            pass  # 可能用户只是双击打开了目录
        # _thread.start_new_thread(, (file,))  # 读取文件

    def readTextFile(self, f: PyQt5.QtCore.QModelIndex):
        """读取文本文件"""
        print(f)
        self.fName = ntpath.join(self.fileListModel.filePath(f))  # 获取文件名称和路径
        self.focusedFileContent = builtins.open(self.fName, "r").read()  # 读取文件
        self.continue_displayFile(self.focusedFileContent)  # 继续读取
        return self.focusedFileContent  # 返回内容

    def continue_displayFile(self, f):
        """继续显示内容"""
        print("正在创建标签页...")
        tabMgr = self.newTab()  # 为新文件打开标签页
        print("创建ed")
        tabMgr.setContent(f, text=self.fName)  # 读取并设置

    def currentChanged(self, changedPage):
        """更改tabWidget的当前页面切换"""
        if changedPage == 5:
            self.dockWidget_2.close()  # 关闭当前控制台窗口

    def setActions(self):
        """设置动作"""
        self.action_Exit.triggered.connect(exit)  # 绑定退出菜单槽
        self.action_Open.triggered.connect(self.openFile)  # 绑定打开文件槽
        # self.action_
        self.action_Compile.triggered.connect(self.run)  # 运行代码

        self.tabWidget_2.currentChanged.connect(self.currentChanged)  # 绑定tabWidget的默认页面更改事件

        self.tabWidget.setTabsClosable(True)  # 可以关闭标签
        self.tabWidget.tabCloseRequested.connect(self.closeTab)  # 关闭标签

        # 声明在groupBox创建右键菜单
        self.browsers = [self.widget_ace_1, self.widget_ace_2]
        for self.br in self.browsers:
            self.br.setContextMenuPolicy(3)
            self.br.customContextMenuRequested.connect(lambda: self.create_rightmenu(self.br))  # 连接到菜单显示函数

    def closeTab(self, tabIndex):
        if len(self.tabs) != 1:
            del self.tabs[tabIndex]  # 删除关闭的记录
            self.tabWidget.removeTab(tabIndex)  # 移除内容
        else:
            try:
                self.btnBox = systemStyleBtnBox.SystemBtnBox(title="选择关闭所有标签页的动作", msg="您正在尝试关闭所有页面,您的意思是:",
                                                             choices=["关闭软件", "保持空白", "保留一个空标签页", "取消关闭"])
                reply = self.btnBox.run()  # 显示选择窗口
                if reply == "关闭软件":
                    exit(0)  # 退出程序
                elif reply == "保持空白":
                    del self.tabs[tabIndex]  # 删除关闭的记录
                    self.tabWidget.removeTab(tabIndex)  # 移除内容
                elif reply == "保留一个空标签页":
                    del self.tabs[tabIndex]  # 删除关闭的记录
                    self.tabWidget.removeTab(tabIndex)  # 移除内容
                    self.newTab()  # 新空标签页
                else:  # 取消关闭
                    return  # 啥也不干直接退出函数
            except Exception:
                self.virtualTraceback()  # 显示报错

    def newTab(self):
        """新空标签页"""
        print("新标签页创建")
        newTab = QtWidgets.QWidget(self.tabWidget)
        self.tabs += [newTab]  # 添加记录
        print("记录已添加")
        try:
            self.tabWidget.addTab(newTab, "新标签页")
            print("页面已添加")
            self.browsers += [editor.AceEditor(newTab)]
            self.browsers[-1].setGeometry(mainwindow.QtCore.QRect(0, 0, 1201, 621))  # 设置大小
            self.tabMangers += [self.TabMgr(newTab, self.tabWidget, tabIndex=self.allIndex, mw=self)]  # 创建管理器
            print("管理器已添加")
            self.allIndex += 1  # 总索引增加
            return self.tabMangers[-1]  # 返回管理器
        except Exception:
            self.virtualTraceback()  # 虚拟报错(Qt5不让报错)

    def openFile(self):
        """打开文件"""
        try:
            if ntpath.exists("C:\\Users\\" + getpass.getuser() + "\\CppMakerProjects"):  # 默认项目目录
                pass
            else:
                os.makedirs("C:\\Users\\" + getpass.getuser() + "\\CppMakerProjects")  # 创建目录
            file = mainwindow.QtWidgets.QFileDialog.getOpenFileNames(None, "打开文件",
                                                                     "C:\\Users\\" + getpass.getuser() +
                                                                     "\\CppMakerProjects",
                                                                     "C++文件(*.cpp);;C语言文件(*.c);;头文件("
                                                                     "*.h);;动态链接库(*.dll);;可执行文件(*.exe);;其他文件("
                                                                     "*.*)")
            if not file:
                return  # 用户取消了选择
            else:  # 继续选择
                if type(file) == list:
                    file = [file][0][0]  # 转换列表
                self.addFiles(self.readFiles(file))  # 添加文件
        except Exception:
            self.virtualTraceback()  # 报错

    @staticmethod
    def readFiles(file):
        """读取文件"""
        returner = []  # 文件列表
        for i in file[0]:
            print(i)
            returner += [[
                builtins.open(i, "rb"),
                i.split("\\")[-1] if '\\' in i else i.split("/")[-1]]]  # 读取字节(可能包含图片文件)
            print(i.split("\\")[-1] if '\\' in i else i.split("/")[-1])
        return returner  # 全部返回

    def addFiles(self, file):
        """添加文件"""
        if self.projectDir:  # 项目目录检测
            pass
        else:
            try:
                os.makedirs(
                    ntpath.join("c:\\Users\\", getpass.getuser(), "CppMakerProjects", "defaultProject"))  # 创建默认项目
            except builtins.FileExistsError:
                pass  # 创建过了
            self.projectDir = ntpath.join("c:\\Users\\", getpass.getuser(), "CppMakerProjects",
                                          "defaultProject")  # 默认目录
        new = list(file)
        print(new)
        for i in new:
            print("i:", i)
            with i[0] as f:
                with open(ntpath.join(self.projectDir, i[1]), "wb+") as newF:  # 写入方式打开文件
                    print(ntpath.join(self.projectDir, i[1]), "写入完毕")
                    newF.write(f.read())  # 写入文件
                newF.close()  # 关闭新文件
            f.close()  # 关闭旧文件

    @staticmethod
    def virtualTraceback():
        """显示虚拟报错(Qt5框架不支持显示错误)"""
        print(traceback.format_exc(), file=sys.stderr)  # 显示错误(Qt5默认不显示)

    def copy(self):
        """复制"""
        try:
            self.keyboardMan.press(self.keyboardMan.keyDct["ctrl"])  # Ctrl
            self.keyboardMan.pressOneTime("c")  # +C
            self.keyboardMan.release(self.keyboardMan.keyDct["ctrl"])  # 放开Ctrl
        except Exception:
            self.virtualTraceback()

    def paste(self):
        """粘贴"""
        try:
            self.keyboardMan.press(self.keyboardMan.keyDct["ctrl"])  # Ctrl
            self.keyboardMan.pressOneTime("v")  # +V
            self.keyboardMan.release(self.keyboardMan.keyDct["ctrl"])  # 放开Ctrl
        except Exception:
            self.virtualTraceback()

    def cut(self):
        """剪切"""
        try:
            self.keyboardMan.press(self.keyboardMan.keyDct["ctrl"])  # Ctrl
            self.keyboardMan.pressOneTime("x")  # +X
            self.keyboardMan.release(self.keyboardMan.keyDct["ctrl"])  # 放开Ctrl
        except Exception:
            self.virtualTraceback()

    def create_rightmenu(self, br):
        """创建菜单"""
        try:
            self.browser_Menu = stdqt5.QMenu(self)  # 创建菜单
            self.actionA = mainwindow.QtWidgets.QAction(QIcon('image/保存.png'), u'重新加载此编辑器', self)  # 创建菜单选项对象
            self.actionA.setShortcut("Ctrl+R")  # 刷新快捷键
            self.actionB = mainwindow.QtWidgets.QAction(QIcon(''), u"复制内容\tCtrl+C", self)
            self.actionC = mainwindow.QtWidgets.QAction(QIcon(''), u"粘贴内容\tCtrl+V", self)
            self.actionD = mainwindow.QtWidgets.QAction(QIcon(''), u"剪切内容\tCtrl+X", self)
            self.actionE = mainwindow.QtWidgets.QAction(QIcon(''), u"调试该编辑器", self)
            self.actionF = mainwindow.QtWidgets.QAction(QIcon(''), u"调试/运行该文件")
            self.actionG = mainwindow.QtWidgets.QAction(QIcon(''), u"保存该文件", self)
            self.browser_Menu.addAction(self.actionA)  # 把动作A选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addSeparator()  # 添加分割线
            self.browser_Menu.addAction(self.actionB)  # 把动作B选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addAction(self.actionC)  # 把动作C选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addAction(self.actionD)  # 把动作D选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addSeparator()  # 添加分割线
            self.browser_Menu.addAction(self.actionE)  # 把动作E选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addAction(self.actionF)  # 把动作F选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.addSeparator()  # 添加分割线
            self.browser_Menu.addAction(self.actionG)  # 把动作F选项对象添加到菜单self.groupBox_menu上
            self.browser_Menu.popup(QCursor.pos())  # 显示菜单
            self.actionA.triggered.connect(br.reload)  # 重新加载
            self.actionB.triggered.connect(self.copy)  # 复制
            self.actionC.triggered.connect(self.paste)  # 粘贴
            self.actionD.triggered.connect(self.cut)  # 剪切
            self.actionE.triggered.connect(self.startDebugger)  # 调试
            self.actionF.triggered.connect(self.run)  # 运行
            self.actionG.triggered.connect(self.save_)  # 保存

        except Exception:
            self.virtualTraceback()

    def compile(self):
        """编译"""
        self.save_()  # 保存
        if not ntpath.exists(ntpath.join(self.projectDir, ".cppmaker/run.txt")):
            self.runWin = self.RunWindow()  # 创建设置窗口
            self.runWin.show()  # 显示窗口

    def VC_CL_Run(self):
        """使用visualC++的CL来运行"""
        cl_output = os.popen("")  # 即将填入cl路径

    def save_(self):
        """保存槽"""
        try:
            mgr: self.TabMgr = self.tabMangers[self.tabWidget.currentIndex()]  # 获取当前管理器
            mgr.save()  # 保存当前文件
        except Exception:
            self.virtualTraceback()

    class RunWindow(runWin.Ui_MainWindow, runWin.QtWidgets.QMainWindow):
        """运行设置"""

        def __init__(self):
            """初始化"""
            runWin.Ui_MainWindow.__init__(self)  # 初始化UI
            super(runWin.QtWidgets.QMainWindow).__init__()  # 初始化窗口
            self.setupUi(self)  # 绑定窗口到UI


if __name__ == '__main__':
    global debuggerPort
    debuggerPort = random.randint(0, 9000)
    app = QtWidgets.QApplication(['', "--remote-debugging-port=" + str(debuggerPort)])  # 创建应用对象
    mw = MainWindow()  # 创建主窗口

    # 窗口跳到最前端并显示
    self = mw
    self.activateWindow()
    self.setWindowState(int(self.windowState()) & ~Qt.WindowMinimized | Qt.WindowActive)
    self.showNormal()
    tk.withdraw()  # 隐藏启动窗口(因为软件启动完成)
    exit(app.exec_())  # 运行主循环并返回退出代码

结果,你点保存,可以保存,但是编译时自动调用函数进行保存,闪退

try没有用,逐步排虫发现是runJavaScript的问题,压根没有运行到回调,请问怎么解决? 

  • 写回答

1条回答 默认 最新

  • hsu8999 2023-01-30 10:21
    关注

    QT5的runJavaScript函数是异步的,因此它只能在槽中调用。这是因为槽是一种可以等待其他事件完成的机制,而JavaScript代码的执行是一个事件。如果不在槽中调用runJavaScript函数,则不能保证JavaScript代码已经完成执行。

    评论

报告相同问题?

问题事件

  • 创建了问题 3月21日

悬赏问题

  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题
  • ¥15 C#算法问题, 不知道怎么处理这个数据的转换
  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!