How to rebuild panels in wxpython in function of a variable? - variables

I searched a lot to do this but nothing of what I tried work. Now, as last attemp, I am trying with pubsub, but I can get nothing of worth, that's why I am asking for help now :). This is an minimal (as best I can do it :)) example of what I want. PanelB gets information in a list (box), and when someone of the items is selected, PanelA should change according to him.
Thank you in advance.
from wx.lib.pubsub import Publisher
import wx
global name
name = 'none, please select an item'
class PanelA(wx.Panel):
def __init__(self, parent, name):
wx.Panel.__init__(self, parent)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
str = name
txt = wx.StaticText(self, -1, "You have selected " + str, (20, 100))
self.hbox.Add(txt, 1, wx.EXPAND | wx.ALL, 20)
class PanelB(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.listbox = wx.ListBox(self, -1)
self.hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20)
self.btnPanel = wx.Panel(self, -1)
self.new = wx.Button(self.btnPanel,label='Add', size=(90, 30))
self.new.Bind(wx.EVT_BUTTON, self.NewItem)
self.vbox.Add((-1, 20))
self.vbox.Add(self.new)
self.btnPanel.SetSizer(self.vbox)
self.hbox.Add(self.btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20)
self.SetSizer(self.hbox)
self.Bind(wx.EVT_LISTBOX, self.onSelect)
def onSelect(self, event):
name_selected = self.listbox.GetStringSelection()
Publisher().sendMessage(("ListBox"), name_selected)
def NewItem(self, event):
text = wx.GetTextFromUser('Nombre', 'Programa a salvar')
if text != '':
self.listbox.Append(text)
class MainFrame(wx.Frame):
def __init__(self, parent, id, title, *args, **kw):
wx.Frame.__init__(self, parent, id, title, size = (800,300))
self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D)
self.lc1 = PanelB(self.splitter)
Publisher().subscribe(self.OnSelect, ("ListBox"))
self.lc2 = PanelA(self.splitter, name)
self.splitter.SplitVertically(self.lc1, self.lc2)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.splitter, 1, wx.EXPAND)
self.SetSizer(sizer)
self.Centre()
self.Show(True)
def OnSelect(self, name_selected):
name = name_selected
#I stucked here
if __name__ == "__main__":
app = wx.App()
frame = MainFrame(None,-1,'Mi aplicacion')
app.MainLoop()

This is not beautiful solution, basically you can just destroy PanelA then call that panel again, maybe like this:
def OnSelect(self, name_selected):
self.lc2.Destroy()
self.lc2 = PanelA(self.splitter, name_selected.data)
Hope this can help.
UPDATE (08/24/2012): added some code
Okey we should not destroy the panel. I'm using your code and modified it a bit. I removed global variable name and added changeName(name) function to PanelA so that when MainFrame.onSelect() is called it will call changeName(). It's not beautiful but hope this can help.
import wx
from wx.lib.pubsub import Publisher
class PanelA(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
name = "none, please select an item"
self.txt = wx.StaticText(self, -1, "You have selected " + name,
(50, 100))
self.hbox.Add(self.txt, 1, wx.EXPAND|wx.ALL, 30)
def changeName(self, name):
self.hbox.Hide(self.txt)
self.hbox.Remove(self.txt)
self.txt = wx.StaticText(self, -1, "You have selected " + name, (50, 100))
self.hbox.Add(self.txt, 1, wx.EXPAND|wx.ALL, 30)
class PanelB(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.hbox = wx.BoxSizer(wx.HORIZONTAL)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.listbox = wx.ListBox(self, -1)
self.hbox.Add(self.listbox, 1, wx.EXPAND|wx.ALL, 20)
self.btnPanel = wx.Panel(self, -1)
self.new = wx.Button(self.btnPanel,label='Add', size=(90, 30))
self.new.Bind(wx.EVT_BUTTON, self.NewItem)
self.vbox.Add((-1, 20))
self.vbox.Add(self.new)
self.btnPanel.SetSizer(self.vbox)
self.hbox.Add(self.btnPanel, 0.6, wx.EXPAND|wx.RIGHT, 20)
self.SetSizer(self.hbox)
self.Bind(wx.EVT_LISTBOX, self.onSelect)
def onSelect(self, event):
name_selected = self.listbox.GetStringSelection()
Publisher().sendMessage("ListBox", name_selected)
def NewItem(self, event):
text = wx.GetTextFromUser('Nombre', 'Programa a salvar')
if text != '':
self.listbox.Append(text)
class MainFrame(wx.Frame):
def __init__(self, parent, id, title, *args, **kw):
wx.Frame.__init__(self, parent, id, title, size = (800,300))
self.splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D)
Publisher().subscribe(self.OnSelect, "ListBox")
self.lc1 = PanelB(self.splitter)
self.lc2 = PanelA(self.splitter)
self.splitter.SplitVertically(self.lc1, self.lc2)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.sizer.Add(self.splitter, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.Layout()
self.Centre()
def OnSelect(self, name_selected):
name = name_selected
self.lc2.changeName(name.data)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MainFrame(None,-1,'Mi aplicacion')
frame.Show()
app.MainLoop()

Related

Why does QThread freezes Gui Thread while working?

I need to keep updating the table to have up-to-date information. To do this, I created a Thread and implemented an infinite loop in it, which iterates through all the values and compares them, but for some reason the work of this thread affects GUI and as a result, the thread with GUI freezes.
import random
import sys
import pandas as pd
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QObject, pyqtSignal, Qt, QThread, QModelIndex
from PyQt5.QtWidgets import QApplication, QMainWindow
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, header: list):
super(TableModel, self).__init__()
self._data = pd.DataFrame(columns=header)
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data.values[index.row()][index.column()]
return None
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self._data.values)
def columnCount(self, index):
return self._data.columns.size
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def setData(self, index, value, role):
if not index.isValid():
return False
row = index.row()
if row < 0 or row >= len(self._data.values):
return False
column = index.column()
if column < 0 or column >= self._data.columns.size:
return False
self._data.iloc[row, column] = value
self.dataChanged.emit(index, index)
return True
def removeRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end < self.rowCount(parent):
self.beginRemoveRows(parent, start, end)
for index in range(start, end + 1):
self._data.drop(index, inplace=True)
self._data.reset_index(drop=True, inplace=True)
self.endRemoveRows()
return True
return False
def insertRows(self, position, rows, parent=QModelIndex()):
start, end = position, position + rows - 1
if 0 <= start <= end:
self.beginInsertRows(parent, start, end)
for index in range(start, end + 1):
default_row = [[None] for _ in range(self._data.shape[1])]
new_df = pd.DataFrame(dict(zip(list(self._data.columns), default_row)))
self._data = pd.concat([self._data, new_df])
self._data = self._data.reset_index(drop=True)
self.endInsertRows()
return True
return False
def addRow(self, item):
self.insertRows(self.rowCount(), 1)
row = self.rowCount() - 1
self.setData(self.index(row, 0), item[0], Qt.DisplayRole)
self.setData(self.index(row, 1), item[1], Qt.DisplayRole)
self.setData(self.index(row, 2), item[2], Qt.DisplayRole)
self.setData(self.index(row, 3), item[3], Qt.DisplayRole)
self.setData(self.index(row, 4), item[4], Qt.DisplayRole)
def getTable(self):
return self._data
class Updater(QObject):
addRow = pyqtSignal(object)
def __init__(self, table: TableModel):
super().__init__()
self.items = []
for i in range(50):
item = [
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
random.randint(10, 100),
]
self.items.append(item)
self._table = table
self._status = False
def start(self):
self._status = True
if self._table.getTable().empty:
items = self.items
for item in items:
self.addRow.emit(item)
while self._status:
self.Update(self.items)
def Update(self, items: list):
# This function is looking for items in table and changes data if its not up-to-date
data = self._table.getTable()
for item in items:
result = data.loc[
(data['Col1'] == item[0]) &
(data['Col2'] == item[1]) &
(data['Col3'] == item[2])
]
if len(result.values) != 0:
pass
# Code ...
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setObjectName("MainWindow")
self.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.tableWidget = QtWidgets.QTableView(self.centralwidget)
self.tableWidget.setObjectName("tableWidget")
self.gridLayout.addWidget(self.tableWidget, 0, 0, 1, 1)
self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
self.setCentralWidget(self.centralwidget)
header = ["Col1", "Col2", "Col3", "Col4", "Col5"]
self.model = TableModel(header)
self.tableWidget.setModel(self.model)
self._updater = Updater(self.model)
self._thread = QThread()
self._updater.moveToThread(self._thread)
self._thread.started.connect(self._updater.start)
self._thread.finished.connect(self._thread.deleteLater)
self._updater.addRow.connect(self.model.addRow, Qt.QueuedConnection)
self.show()
self._thread.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
Do not judge strictly. I've only recently started learning pyqt and I don't really understand it yet.Thank you in advance!

How to open context menu for an object that is empty, but has a size?

I am creating a GUI for a dependency graphing software... And am not able to figure out how to get a context menu to open for my lines.
What I want to do, right click on/near a MyLine widget and open a context menu... What is happening right clicks are not detected.
It is currently not detecting right clicks on the line widgets location to open a context menu (Purpose of this is to allow the user to delete/edit lines by right clicking on them).
What am I doing wrong here?
class MyLine(QWidget):
def __init__(self, destination: Node, source: Node, parent=None):
super().__init__(parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
self.destination = destination
self.source = source
self.setAutoFillBackground(True)
p = self.palette()
p.setColor(self.backgroundRole(), Qt.red)
self.setPalette(p)
def update_line_size(self):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
leftcornerX = origin.x() if origin.x() < destination.x() else destination.x()
leftcornerY = origin.y() if origin.y() < destination.y() else destination.y()
sizeX = abs(origin.x() - destination.x())
sizeY = abs(origin.y() - destination.y())
self.setGeometry(leftcornerX, leftcornerY, sizeX, sizeY)
def showMenu(self, _):
menu = QMenu()
menu.addAction("Delete", self.remove)
menu.exec_(self.cursor().pos())
def draw(self, painter: QPainter):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
painter.drawLine(origin, destination)
# DRAW ARROW HEAD
ARROW_SIZE = 10 # Might change
line_angle = calculate_line_angle(destination, origin)
draw_arrow_head(destination, painter, line_angle, ARROW_SIZE)
def remove(self):
self.parent().delete_line(self)
self.deleteLater()
Edit:
required types for reproducibility
class Node(QLabel):
def __init__(self, text: str, parent=None):
super().__init__(text, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
def get_line_draw_pos(self, other_side: QPoint):
x = self.pos().x() if other_side.x() < self.pos().x() else (self.pos().x() + self.width())
y = self.pos().y() if other_side.y() < self.pos().y() else (self.pos().y() + self.height())
return QPoint(x, y)
def showMenu(self, _):
pass #purposefully left as a stub
def calculate_line_angle(destination: QPoint, origin: QPoint):
return math.atan2(destination.y() - origin.y(), destination.x() - origin.x())
def draw_arrow_head(destination: QPoint, painter: QPainter, line_angle: float, arrow_size: float = 10):
angle1 = math.radians(22.5) + line_angle
angle2 = math.radians(-22.5) + line_angle
arrow1 = QPoint( int(destination.x() - arrow_size * math.cos(angle1)), int(destination.y() - arrow_size * math.sin(angle1)))
arrow2 = QPoint( int(destination.x() - arrow_size * math.cos(angle2)), int(destination.y() - arrow_size * math.sin(angle2)))
painter.drawLine(destination, arrow1)
painter.drawLine(destination, arrow2)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setAcceptDrops(True) # add a drop rule
self.setMouseTracking(True)
self.track_origin = None
self.track_mouse = QPoint(0,0)
self.lines = []
def paintEvent(self, event):
painter = QPainter(self)
for line in self.lines:
line.draw(painter)
line.update_line_size()
def connectNodes(self, destination: Node, source: Node):
self.lines.append(MyLine(destination, source))
self.update()
def delete_line(self, line: MyLine):
self.lines.remove(line)
self.update()
app = QApplication([])
window = MainWindow()
window.setWindowTitle("Right Click to remove label")
window.setGeometry(100, 100, 400, 200)
window.move(60,15)
nodes = []
for index, node_name in enumerate(["hello.txt", "not_a_villain.txt", "nope.txt"]):
node = Node(node_name, window)
node.move(50 + index*100, 50 + (index%2) * 50)
nodes.append(node)
window.connectNodes(nodes[0], nodes[1])
window.connectNodes(nodes[0], nodes[2])
window.connectNodes(nodes[1], nodes[2])
window.show()
sys.exit(app.exec_())

Pyqt5 synchronization between two tabwidget which are in different window

I want to make new window by double click tabwidget.
and copy tabwidget's (child which is tablewidget) to new window.
and finally, changing item of new window's tablewidget needs to change mainwindow's tablewidget.
would it be possible?
I have seen this, that answer does copy tabwidget to new window
but remove mainwindow tabwidget.
here is I worked so far.
I managed to make new dialog by double click, but other things.. I dont' have any clues. can anyone can help?
#!/usr/bin/python
# -*- coding: utf8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class DetachableTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self)
self.tabBar = self.TabBar(self)
self.tabBar.onDetachTabSignal.connect(self.detachTab)
self.setTabBar(self.tabBar)
print("DetachableTabWidget")
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
all_list = []
list1 = []
list2 = []
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
for i in range(tablewidgetA.rowCount()):
list1.append(tablewidgetA.item(i, 0).text())
list2.append(tablewidgetA.item(i, 1).text())
all_list.append(list1)
all_list.append(list2)
detachedTab = self.DetachedTab(all_list)
detachedTab.setWindowModality(Qt.NonModal)
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
# def __init__(self, contentWidget, parent=None):
def __init__(self, all_list, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableWidget()
table.setColumnCount(len(all_list))
table.setRowCount(len(all_list[0]))
for col in range(len(all_list)) :
for row in range(len(all_list[col])) :
item = QTableWidgetItem(all_list[col][row])
table.setItem(row, col, item)
layout.addWidget(table)
table.show()
class TabBar(QTabBar):
onDetachTabSignal = pyqtSignal(int, QPoint)
onMoveTabSignal = pyqtSignal(int, int)
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)
self.dragStartPos = QPoint()
self.dragDropedPos = QPoint()
self.mouseCursor = QCursor()
self.dragInitiated = False
def mouseDoubleClickEvent(self, event) :
event.accept()
self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralTabs = DetachableTabWidget()
self.setCentralWidget(self.centralTabs)
self.setFixedWidth(600)
self.setFixedHeight(600)
#tab 1
self.tab_1 = QWidget()
self.centralTabs.addTab(self.tab_1,"Tab 1")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
vbox.addWidget(Table)
item = QTableWidgetItem("table 1 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 1, item)
vbox.setAlignment(Qt.AlignTop)
self.tab_1.setLayout(vbox)
#tab 2
self.tab_2 = QWidget()
self.centralTabs.addTab(self.tab_2,"Tab 2")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 5 content")
Table.setItem( 1, 1, item)
vbox.addWidget(Table)
vbox.setAlignment(Qt.AlignTop)
self.tab_2.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
If you want to synchronize data between item views, you have to use a common model. Since you're using a QTableWidget (which has an internal, private model, and a higher level item view) you can create a new window using a QTableView instead, and set its model to the source. In that case, you don't need to "copy" row/column/data, you only need to use the source model.
Here's a modified version of your script:
class DetachableTabWidget(QTabWidget):
# ...
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
detachedTab = self.DetachedTab(tablewidgetA.model())
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
def __init__(self, model, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableView()
table.setModel(model)
layout.addWidget(table)
table.show()
With this code you can modify the "child" window table data, and it will always synchronize the source table widget.

PyQt5: detect a new inserted tab with QTabWidget::tabInserted(int index)

I'm new in PyQt5. I made a tabWidget where I was able to connect a button to add tabs dynamically and remove/close them. Before inserting tabs with button.Clicked, there's one open (already inserted) tab saying "no tabs are open". I would like to close this tab after inserting a new tab and reopen the tab after closing all new tabs (when no tabs are open). I couldn't find any example on how to use QTabWidget::tabInserted(int index) from http://doc.qt.io/qt-5/qtabwidget.html. Does anyone know how to use tabInserted and tabRemoved to detect when triggered. I would like to increment each time a tab is inserted and decrement when removed, to know when there are open tabs and no open tabs. Thanks
self.toolButton.clicked.connect(self.button_addtab)
self.tabWidget.tabCloseRequested.connect(self.close_tab)
def button_addtab(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.tabWidget.addTab(self.tab_2, "")
self.tabWidget.setCurrentIndex(pages-0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Second_tab"))
def close_tab(self, index):
self.tabWidget.removeTab(index)
I tried this:
if self.tabWidget.count() <= 0:
#Add the "no tab open" tab
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.tabWidget.addTab(self.tab_3, "")
_translate = QtCore.QCoreApplication.translate
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Info"))
elif self.tabWidget.count() > 0:
self.tabWidget.removeTab(self.tabWidget.indexOf(self.tab_3))
This work, but it doesn't add after closing and it reopens after one more tab is added. That's why I would like to use tabInserted
See class TabWidget
import sys
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtGui import QColor, QPainter
from PyQt5.QtWidgets import (QApplication, QWidget, QMainWindow, QAction,
QVBoxLayout, QTabWidget, QFileDialog, QPlainTextEdit, QHBoxLayout)
lineBarColor = QColor(53, 53, 53)
lineHighlightColor = QColor('#00FF04')
class TabWidget(QTabWidget):
def __init__(self, parent=None):
super(TabWidget, self).__init__(parent)
# This virtual handler is called after a tab was removed from position index.
def tabRemoved(self, index):
print("\n tab was removed from position index -> {}".format(index))
# This virtual handler is called after a new tab was added or inserted at position index.
def tabInserted(self, index):
print("\n New tab was added or inserted at position index -> {}".format(index))
class NumberBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.editor = parent
layout = QVBoxLayout(self)
self.editor.blockCountChanged.connect(self.update_width)
self.editor.updateRequest.connect(self.update_on_scroll)
self.update_width('001')
def mousePressEvent(self, QMouseEvent):
print("\n - class NumberBar(QWidget): \n\tdef mousePressEvent(self, QMouseEvent):")
def update_on_scroll(self, rect, scroll):
if self.isVisible():
if scroll:
self.scroll(0, scroll)
else:
self.update()
def update_width(self, string):
width = self.fontMetrics().width(str(string)) + 10
if self.width() != width:
self.setFixedWidth(width)
def paintEvent(self, event):
if self.isVisible():
block = self.editor.firstVisibleBlock()
height = self.fontMetrics().height()
number = block.blockNumber()
painter = QPainter(self)
painter.fillRect(event.rect(), lineBarColor)
painter.setPen(Qt.white)
painter.drawRect(0, 0, event.rect().width() - 1, event.rect().height() - 1)
font = painter.font()
current_block = self.editor.textCursor().block().blockNumber() + 1
while block.isValid():
block_geometry = self.editor.blockBoundingGeometry(block)
offset = self.editor.contentOffset()
block_top = block_geometry.translated(offset).top()
number += 1
rect = QRect(0, block_top, self.width() - 5, height)
if number == current_block:
font.setBold(True)
else:
font.setBold(False)
painter.setFont(font)
painter.drawText(rect, Qt.AlignRight, '%i' % number)
if block_top > event.rect().bottom():
break
block = block.next()
painter.end()
class Content(QWidget):
def __init__(self, text):
super(Content, self).__init__()
self.editor = QPlainTextEdit()
self.editor.setPlainText(text)
# Create a layout for the line numbers
self.hbox = QHBoxLayout(self)
self.numbers = NumberBar(self.editor)
self.hbox.addWidget(self.numbers)
self.hbox.addWidget(self.editor)
class MyTableWidget(QWidget):
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Initialize tab screen
self.tabs = TabWidget() #QTabWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.setTabsClosable(True)
self.tabs.tabCloseRequested.connect(self.closeTab)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
def closeTab(self, index):
tab = self.tabs.widget(index)
tab.deleteLater()
self.tabs.removeTab(index)
def addtab(self, content, fileName):
self.tabs.addTab(Content(str(content)), str(fileName))
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.open()
self.tabs = MyTableWidget()
self.setCentralWidget(self.tabs)
self.initUI()
self.show()
def initUI(self):
self.statusBar()
menu = self.menuBar()
fileMenu = menu.addMenu('File')
fileMenu.addAction(self.openAct)
self.resize(800, 600)
def closeTab(self, index):
tab = self.tabs.widget(index)
tab.deleteLater()
self.tabs.removeTab(index)
def buttonClicked(self):
self.tabs.addTab(Content("smalltext2"), "sadsad")
def open(self):
self.openAct = QAction('Open...', self)
self.openAct.setShortcut('Ctrl+O')
self.openAct.setStatusTip('Open a file')
self.is_opened = False
self.openAct.triggered.connect(self.openFile)
def openFile(self):
options = QFileDialog.Options()
filenames, _ = QFileDialog.getOpenFileNames(
self, 'Open a file', '',
'Python Files (*.py);;Text Files (*.txt)',
options=options
)
if filenames:
for filename in filenames:
with open(filename, 'r+') as file_o:
try:
text = file_o.read()
self.tabs.addtab(text, filename)
except Exception as e:
print("Error: filename=`{}`, `{}` ".format( filename, str(e)))
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())

Problem setting value of wx.TextCtrl in wxPython

I'm trying to pass the directory the user chooses back to the wx.TextCtrl named ChooseRoot. This has not worked so far.
What am I doing wrong? I tried different things, but either it hangs or I get this error message.
Traceback (most recent call last):
File "F:\Indexinator3000_x64.pyw", line 78, in OnChooseRoot
self.ChooseRoot.SetValue("Hello")
AttributeError: 'MainPanel' object has no attribute 'ChooseRoot'
import wx
ID_EXIT = 110
class MainPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.parent = parent
#------------- Setting up the buttons
self.Run = wx.Button(self, label="Run")
self.Run.Bind(wx.EVT_BUTTON, self.OnRun )
self.ExitB = wx.Button(self, label="Exit")
self.ExitB.Bind(wx.EVT_BUTTON, self.OnExit)
#------------- Setting up Static text
self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ")
self.labelScratchWrk = wx.StaticText(self, label ="Sratch workspace: ")
self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")
#------------ Setting up inputtext
self.ChooseRoot = wx.TextCtrl(self, -1, size=(210,-1))
self.ChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
self.ScratchWrk = wx.TextCtrl(self, -1, size=(210,-1))
self.MergeFile = wx.TextCtrl(self, -1, size=(210,-1))
#------------- Setting up the outputbox
self.Output = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY)
#------------- Setting up the sizer
SizerF = wx.FlexGridSizer(3,2,5,5)
SizerF.Add(labelChooseRoot) #row 1, col 1
SizerF.Add(ChooseRoot) #row 1, col 2
SizerF.Add(labelScratchWrk) #row 2, col 1
SizerF.Add(ScratchWrk) #row 2, col 2
SizerF.Add(labelMergeFile) #row 3, col 1
SizerF.Add(MergeFile) #row 3, col 2
SizerB = wx.BoxSizer(wx.VERTICAL)
SizerB.Add(Run, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
SizerB.Add(ExitB, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
Sizer1 = wx.BoxSizer()
Sizer1.Add(SizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
Sizer1.Add(SizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
Sizer2 = wx.BoxSizer()
Sizer2.Add(Output, 1, wx.EXPAND | wx.ALL, 5)
SizerFinal = wx.BoxSizer(wx.VERTICAL)
SizerFinal.Add(Sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
SizerFinal.Add(Sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.SetSizerAndFit(SizerFinal)
#--- START EVENT HANDLERS
def OnChooseRoot(self, Event=None):
# In this case we include a "New directory" button.
dlg = wx.DirDialog(self, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE
#| wx.DD_DIR_MUST_EXIST
#| wx.DD_CHANGE_DIR
)
# If the user selects OK, then we process the dialog's data.
# This is done by getting the path data from the dialog - BEFORE
# we destroy it.
if dlg.ShowModal() == wx.ID_OK:
RootPath = dlg.GetPath()
self.ChooseRoot.SetValue(RootPath)
# Only destroy a dialog after you're done with it.
dlg.Destroy()
def OnRun(self, Event=None):
#First check if any of the boxes is empty
pass
def OnExit(self, Event=None):
self.GetParent().Close()
#--- END EVENT HANDLERS
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, wx.ID_ANY, title, size = (430,330),
style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.STAY_ON_TOP)
self.CreateStatusBar()
#------------- Setting up the menu
filemenu = wx.Menu()
filemenu.Append(ID_EXIT, "E&xit", "Exit the program")
#------------- Creating the menu
menubar = wx.MenuBar()
menubar.Append(filemenu, "&File")
self.SetMenuBar(menubar)
#---------- Setting menu event handlers
wx.EVT_MENU(self, ID_EXIT, self.OnExit)
#--- Add MainPanel
self.Panel = MainPanel(self, -1)
self.SetMaxSize(self.GetSize()) #Sets the Maximum size the window can have
self.SetMinSize(self.GetSize()) #Sets the Minimum size the window can have
#Centre on Screen
self.CentreOnScreen()
###---- SHOW THE WINDOW
self.Show(True)
def OnExit(self, event):
self.Close(True) # Close the Frame
#--- END EVENT HANDLERS ---------------------------------
if __name__=='__main__':
try:
app = wx.PySimpleApp()
frame = MainWindow(None, -1, "IndexGenerator")
app.MainLoop()
finally:
del app
Read carefully advices that I have given you in your previous question. Especially:
use object properties for the widgets, so you do not loose track of them (self.ChooseRoot =...)
use more desriptive widget names (self.labelChooseRoot)
Outside of the __init__ method (aka constructor) you loose track of your widgets. You have to add them to your MainPanel object as attributes.
class MainPanel(wx.Panel):
def __init__(self, parent, id):
...
self.ChooseRoot = wx.TextCtrl(self, size=(210, -1))
...
def OnChooseRoot(self, event=None):
...
self.ChooseRoot.SetValue(RootPath)
...
I would also recommend some reading on OOP concepts. Maybe you can start from here.
Edit:
You nearly got it working. The idea was OK, you just forgot a few places. I have updated your code to conform with my "standard", deleted some unnecessary copy/paste stuff and some other minor tweaks. Use some compare software and do a careful compare to see the changes if you like.
import wx
ID_EXIT = 110
class MainPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.buttonRun = wx.Button(self, label="Run")
self.buttonRun.Bind(wx.EVT_BUTTON, self.OnRun )
self.buttonExit = wx.Button(self, label="Exit")
self.buttonExit.Bind(wx.EVT_BUTTON, self.OnExit)
self.labelChooseRoot = wx.StaticText(self, label ="Root catalog: ")
self.labelScratchWrk = wx.StaticText(self, label ="Scratch workspace: ")
self.labelMergeFile = wx.StaticText(self, label ="Merge file: ")
self.textChooseRoot = wx.TextCtrl(self, size=(210, -1))
self.textChooseRoot.Bind(wx.EVT_LEFT_UP, self.OnChooseRoot)
self.textScratchWrk = wx.TextCtrl(self, size=(210, -1))
self.textMergeFile = wx.TextCtrl(self, size=(210, -1))
self.textOutput = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.sizerF = wx.FlexGridSizer(3, 2, 5, 5)
self.sizerF.Add(self.labelChooseRoot) #row 1, col 1
self.sizerF.Add(self.textChooseRoot) #row 1, col 2
self.sizerF.Add(self.labelScratchWrk) #row 2, col 1
self.sizerF.Add(self.textScratchWrk) #row 2, col 2
self.sizerF.Add(self.labelMergeFile) #row 3, col 1
self.sizerF.Add(self.textMergeFile) #row 3, col 2
self.sizerB = wx.BoxSizer(wx.VERTICAL)
self.sizerB.Add(self.buttonRun, 1, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizerB.Add(self.buttonExit, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
self.sizer1 = wx.BoxSizer()
self.sizer1.Add(self.sizerF, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL, 10)
self.sizer1.Add(self.sizerB, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizer2 = wx.BoxSizer()
self.sizer2.Add(self.textOutput, 1, wx.EXPAND | wx.ALL, 5)
self.sizerFinal = wx.BoxSizer(wx.VERTICAL)
self.sizerFinal.Add(self.sizer1, 0, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.sizerFinal.Add(self.sizer2, 1, wx.ALIGN_RIGHT | wx.EXPAND | wx.ALL)
self.SetSizerAndFit(self.sizerFinal)
def OnChooseRoot(self, event):
dlg = wx.DirDialog(self, "Choose a directory:", style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
root_path = dlg.GetPath()
self.textChooseRoot.SetValue(root_path)
dlg.Destroy()
def OnRun(self, event):
#First check if any of the boxes is empty
pass
def OnExit(self, event):
self.GetParent().Close()
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="IndexGenerator", size=(430, 330),
style=((wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE |
wx.STAY_ON_TOP) ^ wx.RESIZE_BORDER))
self.CreateStatusBar()
self.fileMenu = wx.Menu()
self.fileMenu.Append(ID_EXIT, "E&xit", "Exit the program")
self.menuBar = wx.MenuBar()
self.menuBar.Append(self.fileMenu, "&File")
self.SetMenuBar(self.menuBar)
wx.EVT_MENU(self, ID_EXIT, self.OnExit)
self.Panel = MainPanel(self)
self.CentreOnScreen()
self.Show()
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = MainWindow()
app.MainLoop()