How to reimplement QTextDocument.createObject?
this method plays a role in making QTextFrame, QTextList, QTextTable or other QTextObject.
According to woboq, I think my reimplementation is the same.
But kernel stops.
Why? What is my code short of?
from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
import PySide2
import sys
import os
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
class TextEdit(QtWidgets.QTextEdit):
def __init__(self, parent=None):
super(TextEdit, self).__init__(parent=None)
document = TextDocument(self)
self.setDocument(document)
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent=None)
self.setParent(parent)
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
return obj
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
mainwindow = TextEdit()
mainwindow.show()
sys.exit(QtWidgets.QApplication.exec_())
if __name__ == "__main__":
main()
It seems to me that it is a bug (I have tested it with PyQt5 and it works correctly), the problem to be the life cycle of the QTextObject since as in C++ the life cycle is undefined since it is a pointer but being a child of QTextDocument So its life cycle is that of the QTextDocument, but in python it seems that it considers it an object of limited scope(local variable) not respecting the ownership that the QTextDocument has over this since it is its parent. A workaround seems to be making obj an member of the class:
def createObject(self, f):
self.obj = QtGui.QTextObject(self)
if f.isListFormat():
self.obj = QtGui.QTextList(self)
elif f.isTableFormat():
self.obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
self.obj = QtGui.QTextFrame(self)
return self.obj
Or using a container that is a member of the class.
class TextDocument(QtGui.QTextDocument):
def __init__(self, parent=None):
super(TextDocument, self).__init__(parent)
self.objs = []
def createObject(self, f):
obj = QtGui.QTextObject(self)
if f.isListFormat():
obj = QtGui.QTextList(self)
elif f.isTableFormat():
obj = QtGui.QTextTable(self)
elif f.isFrameFormat():
obj = QtGui.QTextFrame(self)
self.objs.append(obj)
return obj
I prefer the second workaround since in the case of the first one you could generate problems if you create several QTextObject since the previous one would be deleted.
Finally I recommend reporting the bug.
The handling of the life cycle of some objects seems a persistent bug in PySide2.
Related
In this post, my goal is to concatenate two QFileSystemModels to one and display them together. (Lots of updates has been made)
Context :
In my C drive , I created the folder MyFolder (https://drive.google.com/drive/folders/1M-b2o9CiohXOgvjoZrAnl0iRVQBD1sXY?usp=sharing) , in which there are some folders and some files, for the sake of producing the minimal reproducible example . Their structure is :
The following Python code using PyQt5 library (modified from How to display parent directory in tree view?) runs after importing necessary libraries:
#The purpose of the proxy model is to display the directory.
#This proxy model is copied here from the reference without modification.
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root_path = ""
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
if self._root_path and isinstance(source_model, QFileSystemModel):
root_index = source_model.index(self._root_path).parent()
if root_index == source_parent:
index = source_model.index(source_row, 0, source_parent)
return index.data(QFileSystemModel.FilePathRole) == self._root_path
return True
#property
def root_path(self):
return self._root_path
#root_path.setter
def root_path(self, p):
self._root_path = p
self.invalidateFilter()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.create_treeview()
self.setCentralWidget(self.treeView_1) #The line I will be talking about.
def create_treeview(self):
self.treeView_1 = QTreeView()
self.dirModel_1 = QFileSystemModel()
self.dirModel_1.setRootPath(QDir.rootPath())
path_1 = 'C:/MyFolder/SubFolder1' # Changing the path is sufficient to change the displayed directory
root_index_1 = self.dirModel_1.index(path_1).parent()
self.proxy_1 = ProxyModel(self.dirModel_1)
self.proxy_1.setSourceModel(self.dirModel_1)
self.proxy_1.root_path = path_1
self.treeView_1.setModel(self.proxy_1)
proxy_root_index_1 = self.proxy_1.mapFromSource(root_index_1)
self.treeView_1.setRootIndex(proxy_root_index_1)
self.treeView_2 = QTreeView()
self.dirModel_2 = QFileSystemModel()
self.dirModel_2.setRootPath(QDir.rootPath())
path_2 = 'C:/MyFolder'
root_index_2 = self.dirModel_2.index(path_2).parent()
self.proxy_2 = ProxyModel(self.dirModel_2)
self.proxy_2.setSourceModel(self.dirModel_2)
self.proxy_2.root_path = path_2
self.treeView_2.setModel(self.proxy_2)
proxy_root_index_2 = self.proxy_2.mapFromSource(root_index_2)
self.treeView_2.setRootIndex(proxy_root_index_2)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
The line self.setCentralWidget(self.treeView_1) gives:
Changing self.setCentralWidget(self.treeView_1) to self.setCentralWidget(self.treeView_2) gives:
Objective:
My goal is to concatenate the two trees together. That is, when click run, the user should be able to see:
The order which they show up does not matter. All I care is that MyFolder and SubFolder1 show up as if they are completely independent items (even though in reality one is a subfolder of the other). I should remark that everything is static. That is, we are not trying to detect any changes on folders or files. The only time we ever need to peak at the existing folders and files will be when we click on run.
Update:
After several days of studying and trying, a major progress has been made. I thank musicamante for the hint of using QTreeWidget. The idea is, as said in comments, traverse through models and gradually move everything into one new QTreeWidget. To avoid freeze, my solution is to ask the QFileSystemModel to fetchMore whenever the user wants to see more (i.e. when the user wants to extend QTreeWidget).
The following code runs and almost solves my problem:
import os
from PyQt5.QtCore import*
from PyQt5.QtWidgets import*
from PyQt5 import QtTest
class To_Display_Folder(QSortFilterProxyModel):
def __init__(self, disables=False, parent=None):
super().__init__(parent)
#self.setFilterRegularExpression(r'^(.*\.dcm|[^.]+)$')
self._disables = bool(disables)
self._root_path = ""
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
#case 1 folder
if self._root_path and isinstance(source_model, QFileSystemModel):
root_index = source_model.index(self._root_path).parent()
if root_index == source_parent:
index = source_model.index(source_row, 0, source_parent)
return index.data(QFileSystemModel.FilePathRole) == self._root_path
return True
'''
#case 2 file
file_index = self.sourceModel().index(source_row, 0, source_parent)
if not self._disables:
return self.matchIndex(file_index)
return file_index.isValid()
'''
#property
def root_path(self):
return self._root_path
#root_path.setter
def root_path(self, p):
self._root_path = p
self.invalidateFilter()
def matchIndex(self, index):
return (self.sourceModel().isDir(index) or
super().filterAcceptsRow(index.row(), index.parent()))
def flags(self, index):
flags = super().flags(index)
if (self._disables and
not self.matchIndex(self.mapToSource(index))):
flags &= ~Qt.ItemIsEnabled
return flags
class Widget_Item_from_Proxy(QTreeWidgetItem):
def __init__(self, index_in_dirModel, parent = None):
super().__init__(parent)
self.setText(0, index_in_dirModel.data(QFileSystemModel.FileNameRole))
self.setText(1, index_in_dirModel.data(QFileSystemModel.FilePathRole))
if os.path.isfile(index_in_dirModel.data(QFileSystemModel.FilePathRole)):
self.setIcon(0,QApplication.style().standardIcon(QStyle.SP_FileIcon))
else:
self.setIcon(0,QApplication.style().standardIcon(QStyle.SP_DirIcon))
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
global treeWidget
treeWidget = QTreeWidget()
self.treeWidget = treeWidget
self.treeWidget.itemExpanded.connect(self.upon_expansion)
self.treeWidget.itemClicked.connect(self.tree_click)
#The following directories will be displayed on the tree.
self.add_path_to_tree_widget('C:/MyFolder')
self.add_path_to_tree_widget('C:/Users/r2d2w/OneDrive/Desktop')
self.add_path_to_tree_widget('C:/')
self.setCentralWidget(self.treeWidget)
def add_path_to_tree_widget(self,path):
dirModel = QFileSystemModel()
dirModel.setRootPath(QDir.rootPath())
dirModel.directoryLoaded.connect(lambda: self.once_loaded(path, dirModel))
def once_loaded(self, path, dirModel):
if dirModel.canFetchMore(dirModel.index(path)):
dirModel.fetchMore(dirModel.index(path))
return
root_index = dirModel.index(path).parent()
proxy = To_Display_Folder(disables = False, parent = dirModel)
proxy.setSourceModel(dirModel)
proxy.root_path = path
proxy_root_index = proxy.mapFromSource(root_index)
origin_in_proxy = proxy.index(0,0,parent = proxy_root_index)
root_item = Widget_Item_from_Proxy(
proxy.mapToSource(origin_in_proxy))
self.treeWidget.addTopLevelItem(root_item)
for row in range(0, proxy.rowCount(origin_in_proxy)):
proxy_index = proxy.index(row,0,parent = origin_in_proxy)
child = Widget_Item_from_Proxy(
proxy.mapToSource(proxy_index),
parent = self.treeWidget.topLevelItem(self.treeWidget.topLevelItemCount()-1))
dirModel.directoryLoaded.disconnect()
#pyqtSlot(QTreeWidgetItem)
def upon_expansion(self, treeitem):
for i in range(0, treeitem.childCount()):
if os.path.isdir(treeitem.child(i).text(1)):
self.add_child_path_to_tree_widget(treeitem.child(i))
def add_child_path_to_tree_widget(self,subfolder_item):
subfolder_path = subfolder_item.text(1)
dirModel = QFileSystemModel()
dirModel.setRootPath(QDir.rootPath())
dirModel.directoryLoaded.connect(lambda: self.child_once_loaded(subfolder_item, subfolder_path,dirModel))
def child_once_loaded(self, subfolder_item, subfolder_path, dirModel):
if dirModel.canFetchMore(dirModel.index(subfolder_path)):
dirModel.fetchMore(dirModel.index(subfolder_path))
return
root_index = dirModel.index(subfolder_path).parent()
proxy = To_Display_Folder(disables = False, parent = dirModel)
proxy.setSourceModel(dirModel)
proxy.root_path = subfolder_path
proxy_root_index = proxy.mapFromSource(root_index)
origin_in_proxy = proxy.index(0,0,parent = proxy_root_index)
root_item = Widget_Item_from_Proxy(
proxy.mapToSource(origin_in_proxy))
folder_item = subfolder_item.parent()
itemIndex = folder_item.indexOfChild(subfolder_item)
folder_item.removeChild(subfolder_item)
folder_item.insertChild(itemIndex, root_item)
for row in range(0, proxy.rowCount(origin_in_proxy)):
proxy_index = proxy.index(row,0,parent = origin_in_proxy)
child = Widget_Item_from_Proxy(
proxy.mapToSource(proxy_index),
parent = root_item)
dirModel.directoryLoaded.disconnect()
#pyqtSlot(QTreeWidgetItem)
def tree_click(self, item):
print(item.text(0))
print(item.text(1))
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Since the bounty period is still not over, I will use the time to post two new questions:
Sometimes, especially when the line self.add_path_to_tree_widget('C:/') is present, the code does not give all directories when we click run. This problem is easily fixed by closing the window and clicking on run again. This problem occurs because the QFileSystemModel does not yet have enough time to traverse through the designated folder. If it has just a little bit more time, it will be able to. I wonder if there is a way to fix this programatically.
The function add_path_to_tree_widget is similar to add_child_path_to_tree_widget. The function once_loaded is similar to child_once_loaded. I wonder if there is a way to write these functions more succinctly.
While not impossible, it's quite difficult to create a unique and dynamic model that is able to access different QFileSystemModel structures.
An easier and simpler implementation, which would be more practical for static purposes, is to use a QTreeWidget and create items recursively.
class MultiBrowser(QTreeWidget):
def __init__(self, *pathList):
super().__init__()
self.iconProvider = QFileIconProvider()
self.setHeaderLabels(['Name'])
for path in pathList:
item = self.createFSItem(QFileInfo(path), self.invisibleRootItem())
self.expand(self.indexFromItem(item))
def createFSItem(self, info, parent):
item = QTreeWidgetItem(parent, [info.fileName()])
item.setIcon(0, self.iconProvider.icon(info))
if info.isDir():
infoList = QDir(info.absoluteFilePath()).entryInfoList(
filters=QDir.AllEntries | QDir.NoDotAndDotDot,
sort=QDir.DirsFirst
)
for childInfo in infoList:
self.createFSItem(childInfo, item)
return item
# ...
multiBrowser = MultiBrowser('path1', 'path2')
For obvious reasons, the depth of each path and their contents will freeze the UI from interaction until the whole structure has been crawled.
If you need a more dynamic approach, consider using the QFileSystemModel as a source for path crawling, along with its directoryLoaded signal, which will obviously require a more complex implementation.
I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.
I want to test a module in my project, but I have a some problem with it. I don't know how I can check the correct opening of the window after the button click.
I have the windows SelectAddingBookTypeWindow and AddSchoolBookWindow.
I want to check that AddSchoolBookWindow will open after I have clicked on the button called selectBtn.
My current test (I have a problem with the last string because I don't know which method I must use):
#pytest.fixture
def open_window(qtbot):
def _press_button(window):
widget = window()
qtbot.addWidget(widget)
widget.show()
qtbot.wait_for_window_shown(widget)
sleep(3)
return widget
return _press_button
class TestSelectAddingBookTypeWindow:
def test_select_schoolbook(self, open_window, qtbot):
widget = open_window(SelectAddingBookTypeWindow)
qtbot.mouseClick(widget.schoolbookSelector, QtCore.Qt.LeftButton)
qtbot.mouseClick(widget.selectBtn, QtCore.Qt.LeftButton)
assert widget.close()
assert AddSchoolBookWindow.isActiveWindow()
My classes:
A. SelectAddingBookTypeWindow
class SelectBookTypeWindow(QDialog, Ui_selectBookTypeWindow):
#logger.catch
def __init__(self, widget1, widget2):
super().__init__()
self.setupUi(self)
self.selectBtn.clicked.connect(lambda x: self.open_new_window(widget1, widget2))
#logger.catch
def open_new_window(self, schoolbook_widget, fictionbook_widget):
if self.schoolbookSelector.isChecked():
widget = schoolbook_widget()
elif self.fictionbookSelector.isChecked():
widget = fictionbook_widget()
widget.show()
self.close()
class SelectAddingBookTypeWindow(SelectBookTypeWindow):
#logger.catch
def __init__(self):
super().__init__(AddSchoolBookWindow, AddFictionBookWindow)
B. AddSchoolBookWindow
class AddSchoolBookWindow(QDialog, Ui_AddSchoolbookWindow):
#logger.catch
def __init__(self):
super().__init__()
self.setupUi(self)
self.widgets = [
self.schoolbookAuthor, self.schoolbookTitle, self.schoolbookPublishHouse,
self.schoolbookPublishYear, self.schoolbookInvoiceNum, self.schoolbookInvoiceDate, self.schoolbookClass,
self.schoolbookCount, self.schoolbookPrice
]
self.schoolbookSaveBtn.clicked.connect(lambda _: self.save_book())
self.schoolbookClearBtn.clicked.connect(lambda _: Utils.clear_fields(self.widgets, self.schoolbookInvoiceDate))
self.schoolbookCancelBtn.clicked.connect(lambda _: self.close())
#logger.catch
def save_book(self):
field_names = Config.COLUMNS['SchoolBook']
user_data = list(map(lambda x: x.text(), self.widgets))
book_sum = Utils.calculate_book_sum(user_data)
user_data.append(str(book_sum))
book = dict(zip(field_names[1:], user_data))
db = DatabaseManager(Config.PATHES['TO_DATABASE']['PROD'])
model = Schoolbook('SchoolBook', db.connection)
model.insert_book(book)
logger.success(f'The {book} success add in the database')
model.model.select()
This is a little part of full code but full code doesn't solve with this problem.
The problem in your case is that the widget is not accessible since it is a local variable, I also doubt that the window will be shown as it should be removed a moment later, so to access it I will create a property:
class SelectBookTypeWindow(QtWidgets.QDialog, Ui_selectBookTypeWindow):
def __init__(self, widget1, widget2):
super().__init__()
self.setupUi(self)
self.selectBtn.clicked.connect(lambda x: self.open_new_window(widget1, widget2))
self._selected_window = None
#property
def selected_window(self):
return self._selected_window
def open_new_window(self, schoolbook_widget, fictionbook_widget):
if self.schoolbookSelector.isChecked():
self._selected_window = schoolbook_widget()
elif self.fictionbookSelector.isChecked():
self._selected_window = fictionbook_widget()
if self.selected_window is not None:
self.selected_window.show()
self.close()
And then in the test you should verify that selected_window is not None and its base class is AddSchoolBookWindow, also avoid using time.sleep(), in the case of the tests use QTest.qWait()
import pytest
from PyQt5 import QtCore, QtTest
from your_package import AddSchoolBookWindow, SelectAddingBookTypeWindow
#pytest.fixture
def open_window(qtbot):
def callback(window):
widget = window()
qtbot.addWidget(widget)
widget.show()
qtbot.wait_for_window_shown(widget)
QtTest.QTest.qWait(3 * 1000)
return widget
return callback
class TestSelectAddingBookTypeWindow:
def test_select_schoolbook(self, open_window, qtbot):
widget = open_window(SelectAddingBookTypeWindow)
assert widget.isVisible()
qtbot.mouseClick(widget.schoolbookSelector, QtCore.Qt.LeftButton)
qtbot.mouseClick(widget.selectBtn, QtCore.Qt.LeftButton)
assert widget.isHidden()
assert widget.selected_window is not None
assert isinstance(widget.selected_window, AddSchoolBookWindow)
assert widget.selected_window.isVisible()
I am designing a program composed of a 3D viewer and a table with Python 3.4 and PySide bindings.
I have created a TableView with this class:
from PySide import QtGui
from PySide.QtCore import Qt
class MyTableView(QtGui.QWidget):
def __init__(self, parent=None):
super(MyTableView, self).__init__()
self.parent = parent
self.title = "Results"
self.initUI()
def initUI(self):
self.grid = QtGui.QGridLayout(self)
self.table = QtGui.QTableView()
self.grid.addWidget(self.table, 0, 0)
# Configure table
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
self.table.setSortingEnabled(True)
self.table.setAlternatingRowColors(True)
self.table.setShowGrid(False)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
and the model with this other class:
class MyModel(QStandardItemModel):
def __init__(self, path, *args, **kwargs):
super(MyModel, self).__init__()
self.path = path
self.parse()
def parse(self):
with open(self.path) as f:
self.mydata = yaml.load(f)
self.setColumnCount(len(self.mydata['headers']) + 1)
self.setHorizontalHeaderLabels(
['ID'] + self.mydata['headers'])
row = 0
for ind, val in self.mydata['rows'].items():
col = 0
self.insertRow(row)
self.setItem(row, col, QStandardItem(ind))
for v in val:
col += 1
self.setItem(row, col, QStandardItem(str(v)))
row += 1
which are then tied together in this controller:
from PySide.QtCore import Qt
class MyController(object):
def __init__(self, model, view):
self.model = model
self.tableview = view.table
self.fill_table()
self.connect_signals()
def fill_table(self):
self.tableview.setModel(self.model)
self.tableview.sortByColumn(0, Qt.AscendingOrder)
def connect_signals(self):
selectionModel = self.tableview.selectionModel()
selectionModel.selectionChanged.connect(self.selection_changed)
def selection_changed(self, selected, deselected):
print("Selection changed.")
Then, the program is executed through this script:
def main():
app = QtGui.QApplication(sys.argv)
MyController(MyModel(sys.argv[1]), MyView())
sys.exit(app.exec_())
if __name__ == '__main__':
main()
(Note that I haven't posted the main window class, but you get the idea)
The table gets rendered OK, but I am not able to connect the selectionChanged signal to the handler (which should udpate the viewer, but for testing purposes it's only a print statement).
What am I doing wrong? Thanks!
[EDIT]
I have discovered that it works if I use a lambda function to call the handler method. Can someone explain why?!
selectionModel.selectionChanged.connect(lambda: self.selection_changed(selectionModel.selectedRows()))
I tried to implement what you wrote and it worked - so I can't be 100% sure of why you are having trouble. But I suspect it is because of what I had to fix to get it to work at all: I had to sort out some problems you have with garbage collection.
In the example code you give, you create a MyController, a MyModel and a MyView. But they will all then be garbage collected (in CPython) since you don't keep a reference to them. If you add a reference to MyController
my_controller = MyController(MyModel(sys.argv[1]), MyView())
you are almost there, but I think the MyTableView might also then be garbage collected since the controller only keeps a reference to the QTableVIew not the MyTableView.
Presumably using the lanbda function changes the references you are preserving - it preserves the controller and the selection model - and that may be why it is working in that case.
Generally it's a good idea to use the Qt parenting mechanism. If you simply parented all these objects on the main window (or their natural parent widget) that would have prevented most of these problems.
Is there a Python version of the C++ class QtSingleApplication from Qt Solutions?
QtSingleApplication is used to make sure that there can never be more than one instance of an application running at the same time.
Here is my own implementation.
It has been tested with Python 2.7 and PySide 1.1.
It has essentially the same interface as the C++ version of QtSingleApplication. The main difference is that you must supply an application unique id to the constructor. (The C++ version by default uses the path to the executable as a unique id; that would not work here because the executable will most likely be python.exe.)
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtNetwork import *
class QtSingleApplication(QApplication):
messageReceived = Signal(unicode)
def __init__(self, id, *argv):
super(QtSingleApplication, self).__init__(*argv)
self._id = id
self._activationWindow = None
self._activateOnMessage = False
# Is there another instance running?
self._outSocket = QLocalSocket()
self._outSocket.connectToServer(self._id)
self._isRunning = self._outSocket.waitForConnected()
if self._isRunning:
# Yes, there is.
self._outStream = QTextStream(self._outSocket)
self._outStream.setCodec('UTF-8')
else:
# No, there isn't.
self._outSocket = None
self._outStream = None
self._inSocket = None
self._inStream = None
self._server = QLocalServer()
self._server.listen(self._id)
self._server.newConnection.connect(self._onNewConnection)
def isRunning(self):
return self._isRunning
def id(self):
return self._id
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow, activateOnMessage = True):
self._activationWindow = activationWindow
self._activateOnMessage = activateOnMessage
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState(
self._activationWindow.windowState() & ~Qt.WindowMinimized)
self._activationWindow.raise_()
self._activationWindow.activateWindow()
def sendMessage(self, msg):
if not self._outStream:
return False
self._outStream << msg << '\n'
self._outStream.flush()
return self._outSocket.waitForBytesWritten()
def _onNewConnection(self):
if self._inSocket:
self._inSocket.readyRead.disconnect(self._onReadyRead)
self._inSocket = self._server.nextPendingConnection()
if not self._inSocket:
return
self._inStream = QTextStream(self._inSocket)
self._inStream.setCodec('UTF-8')
self._inSocket.readyRead.connect(self._onReadyRead)
if self._activateOnMessage:
self.activateWindow()
def _onReadyRead(self):
while True:
msg = self._inStream.readLine()
if not msg: break
self.messageReceived.emit(msg)
Here is a simple test program:
import sys
from PySide.QtGui import *
from QtSingleApplication import QtSingleApplication
appGuid = 'F3FF80BA-BA05-4277-8063-82A6DB9245A2'
app = QtSingleApplication(appGuid, sys.argv)
if app.isRunning(): sys.exit(0)
w = QWidget()
w.show()
app.setActivationWindow(w)
sys.exit(app.exec_())
You can have a look to this blog entry. It is for Pyside but I guess that it will work too with PyQt4.