I'm working on a project where I use a modified QWebView
. I'm getting this error:
Traceback (most recent call last):
File "/home/jorge/coders/universal-scraper/src/customwebview.py", line 63, in mouseMoveEvent
hittestresult = self.currentframe.hitTestContent(event.pos())
RuntimeError: Internal C++ object (PySide.QtWebKit.QWebFrame) already deleted.
I have read about PySide pitfalls already, and I've been saving that QtWebKit.QWebFrame
object as an attribute of my modified QWebView
with the method setframeafterloadfinished
which is called when the page finishes loading, the problem raised after some majors changes that I needed on my modified QWebView
happened, before that, everything went just fine.
Functional and minimal example is here (just be sure to put the file webelementinfo.py
in the same directory as this code before running a test):
#!/usr/bin/env python2
# coding: utf-8
# VENI, SANCTE SPIRITUS
from PySide.QtWebKit import QWebView
from PySide import QtCore, QtGui
try:
from . import webelementinfo
except ValueError:
import webelementinfo
class CustomQWebView(QWebView):
def __init__(self, *args, **kwargs):
""" Init the custom class
"""
super(CustomQWebView, self).__init__(*args, **kwargs)
self.colors = {0: QtGui.QColor(255, 165, 0, 127),
1: QtGui.QColor(135, 206, 235, 127),
2: QtGui.QColor(135, 235, 164, 127),
3: QtGui.QColor(235, 135, 206, 127),
4: QtGui.QColor(235, 164, 135, 127)}
self.color = None
self.currentframe = None
self.element = None
self.loadFinished.connect(self.setframeafterloadfinished)
self.selectCommentsArea()
@QtCore.Slot()
def selectCommentsArea(self):
""" For selecting the comment area
"""
self.setup_rectcolor_area(0)
@QtCore.Slot(QtGui.QMouseEvent)
def mouseMoveEvent(self, event):
super(CustomQWebView, self).mouseMoveEvent(event)
if self.drawrects:
if self.currentframe:
hittestresult = self.currentframe.hitTestContent(event.pos())
element = webelementinfo.WebElement(
hittestresult, self.color, self)
if not self.element:
self.element = element
elif self.element != element:
self.element = element
# FIXME: self.update should draw rects from WebElements too.
self.update()
@QtCore.Slot(QtGui.QPaintEvent)
def paintEvent(self, event):
# draw the content first
super(CustomQWebView, self).paintEvent(event)
if self.drawrects:
# then the rectangle
if self.element:
self.element.update()
def setframeafterloadfinished(self):
self.currentframe = self.page().mainFrame()
def setup_rectcolor_area(self, forarea):
"""Called when we want to select certain area of a web site
This method set-up the painter to a giving color so web elements are
drawn with a rect on top. Also activates the flag to allow painting
inside CustomQWebView.
:param int forarea: For which area we are going to set the painter\\
valid values are: 0 for Comments area, 1 for comment box, 2 for\\
commentator's user name, 3 for comment date and time, 4 for\\
commentary text.
"""
self.drawrects = True
self.color = self.colors[forarea]
# defines what we are looking to select
self.selecttype = forarea
if __name__ == "__main__":
app = QtGui.QApplication([])
mainwn = QtGui.QMainWindow()
mainwn.resize(800, 696)
centralwidget = QtGui.QWidget(mainwn)
centralwidget.resize(800, 600)
gridlayout = QtGui.QGridLayout(centralwidget)
web = CustomQWebView(parent=centralwidget)
gridlayout.addWidget(web, 0, 0, 1)
web.setUrl(QtCore.QUrl("http://duckduckgo.com"))
mainwn.show()
app.exec_()
Here is the other file that have the definition of that new class WebElement
that I write and start to use:
#!/usr/bin/env python2
# coding: utf-8
# VENI, SANCTE SPIRITUS
from PySide.QtWebKit import QWebElement, QWebHitTestResult
from PySide import QtGui
from PySide import QtCore
class WebElement(QtCore.QObject):
""" Holds information of webelements
"""
def __eq__(self, other):
if isinstance(other, WebElement):
return (self.web_element == other.web_element and
self.getrect() == other.getrect())
else:
raise ValueError("Not same objects")
def __ne__(self, other):
if isinstance(other, WebElement):
return (self.web_element != other.web_element and
self.getrect() != other.getrect())
else:
raise ValueError("Not same objects")
def __init__(self, hittestresult, color, parent=None):
super(WebElement, self).__init__(parent)
if (not isinstance(hittestresult, QWebHitTestResult) and
not isinstance(hittestresult, QWebElement)):
raise ValueError(
"Argument passed for 'hittestresult' is not"
" QtWebkit.QWenHitTestResult or QtWebkit.QWebElement instance"
)
if not isinstance(color, QtGui.QColor):
raise ValueError(
"Argument passed for 'color' is not QtGui.QColor instance"
)
try:
self.frame = hittestresult.frame()
except AttributeError:
self.frame = hittestresult.webFrame()
self.frame_scroll_x = self.frame.scrollPosition().x()
self.frame_scroll_y = self.frame.scrollPosition().y()
try:
rect = hittestresult.boundingRect()
except AttributeError:
rect = hittestresult.geometry()
self.element_rect_x = rect.x()
self.element_rect_y = rect.y()
self.element_rect_w = rect.width()
self.element_rect_h = rect.height()
try:
self.web_element = hittestresult.element()
except AttributeError:
self.web_element = hittestresult
self.color = color
self.color_darker = color.darker()
self.color_darker.setAlpha(255)
self.pen = QtGui.QPen(self.color_darker)
self.pen.setWidth(2)
#self.painter = QtGui.QPainter(self.parent)
self.painter = QtGui.QPainter()
self.painter.setPen(self.pen)
def update(self):
""" draw the rect for this element in the CustomQWebView
"""
rect = self.getrect()
rectf = QtCore.QRectF(rect)
self.painter.fillRect(rectf, self.color)
self.painter.drawRect(rectf)
def getrect(self):
""" Return the rect for this WebElement
"""
self.frame_scroll_x = self.frame.scrollPosition().x()
self.frame_scroll_y = self.frame.scrollPosition().y()
rect = QtCore.QRect()
rect.setRect(self.element_rect_x - self.frame_scroll_x,
self.element_rect_y - self.frame_scroll_y,
self.element_rect_w, self.element_rect_h)
return rect
My project should work right as if I didn't change anything, however, with these changes it doesn't. What am I doing wrong? Am I missing something about QWebFrame
s?
First of all, your example is not minimal: the file "webelementinfo.py" is irrelevant (as well as many other parts of your classes), what's causing the problem is the QWebHitTestResult.frame()
method. The code that is sufficient for the error to occur is as follows:
@QtCore.Slot(QtGui.QMouseEvent)
def mouseMoveEvent(self, event):
if self.currentframe:
hittestresult = self.currentframe.hitTestContent(event.pos())
hittestresult.frame() # <- will cause the crash on next mouseMoveEvent
As noted by ekhumoro this looks like a bug in PySide, related to object ownership. You need to avoid calling the frame()
method - it seems it's not really essential in your code. Change WebElement
's constructor to this:
def __init__(self, frame, hittestresult, color, parent=None):
and then:
#try:
# self.frame = hittestresult.frame() <-- DO NOT CALL THIS
#except AttributeError:
# self.frame = hittestresult.webFrame()
self.frame = frame
And pass the frame explicitly:
hittestresult = self.currentframe.hitTestContent(event.pos())
element = webelementinfo.WebElement(
self.currentframe,
hittestresult,
self.color,
self)
With these fixes the "Internal C++ object (PySide.QtWebKit.QWebFrame) already deleted." error does not occur.