Item Checking not possible with UltimateListCtrl in ULC_VIRTUAL mode - macos

Following is the system and software info
Platforms: Windows XP and OSX Lion
Activestate Python 2.7.2
wxPython2.9-osx-cocoa-py2.7 (for OSX)
wxPython2.9-win32-py27 (for Windows XP)
I am trying to create a UltimateListCtrl using ULC_VIRTUAL and ULC_REPORT mode. I would like to know how can I put a checkbox beside the first column of every row and catch the event when a user checks the box. I was able to do the same using UltimateListCtrl without VIRTUAL mode. But, with the ULC_VIRTUAL flag ON, I don't know how to proceed. Following is the code I created, but this still doesn't allow me to check the boxes associated with the first column. Please help.
import wx
import images
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.SetItemCount(1000)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
def getColumnText(self, index, col):
item = self.GetItem(index, col)
return item.GetText()
def OnGetItemText(self, item, col):
return "Item %d, Column %d" % (item,col)
def OnGetItemColumnImage(self, item, col):
return []
def OnGetItemImage(self, item):
return []
def OnGetItemAttr(self, item):
return None
def OnGetItemTextColour(self, item, col):
return None
#def OnGetItemColumnCheck(self, item, col):
#return True
#def OnGetItemCheck(self, item):
#return True
def OnGetItemToolTip(self, item, col):
return None
def OnGetItemKind(self, item):
return 1
def OnGetItemColumnKind(self, item, col):
if col==0:
return self.OnGetItemKind(item)
return 0
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()
Btw, following is the code I used to create the same thing without the VIRTUAL mode. And in this case, I can check the boxes beside the first column data in every row. But, I will be working with tens of thousands of items and I cannot rely on loading the items like below because it is very slow. Hence, I want to use the Virtual List, but I don't know how to get the same functionality in it.
import wx
import images
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
for record_index in range(0,1000):
for field in self.table_fields:
if self.table_fields.index(field)==0:
self.InsertStringItem(record_index, 'Item %d, Column %d' % (record_index,self.table_fields.index(field)),it_kind=1)
else:
self.SetStringItem(record_index, self.table_fields.index(field), 'Item %d, Column %d' % (record_index,self.table_fields.index(field)))
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()

This question has been here for a while and I've been trying to find a solution to this problem around the web (to no avail). I've now solved mine and I'm posting here just in case someone might find the solution useful, or may have an more appropriate one.
Short answer: you will need to manually keep track of checked and unchecked items. To detect which are being checked (clicked), you can bind to the EVT_LIST_ITEM_CHECKING event.
Long answer: First, you will need a way to keep track of which items are checked or not. Then use that for determining what to return for OnGetItemColumnCheck. You could, for example, use a list of item+columns like so:
def __init__(...):
...
self.checked = []
...
def OnGetItemColumnCheck(self, item, column):
item_column = (item, column)
if item_column in self.checked:
return True
else:
return False
You will now need a way to populate that list. To do that, you will need to bind to the EVT_LIST_ITEM_CHECKING and do the appropriate actions:
def __init__(...):
...
self.checked = []
self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
...
def OnCheck(self, event):
item_column = (event.m_itemIndex, event.m_item.GetColumn())
try:
idx = self.checked.index(item_column)
except ValueError:
idx = None
if idx == None:
self.checked.append(item_column)
else:
del(self.checked[idx])
self.Refresh()
The self.Refresh() call is essential as sometimes the checkbox won't get redrawn. After this, you should now be able to check and uncheck items (and that information is easily accessible to boot!). Here is your complete code with the above modifications:
import wx
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.SetItemCount(1000)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
self.checked = []
self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
def OnCheck(self, event):
item_column = (event.m_itemIndex, event.m_item.GetColumn())
try:
idx = self.checked.index(item_column)
except ValueError:
idx = None
if idx == None:
self.checked.append(item_column)
else:
del(self.checked[idx])
self.Refresh()
def getColumnText(self, index, col):
item = self.GetItem(index, col)
return item.GetText()
def OnGetItemText(self, item, col):
return "Item %d, Column %d" % (item,col)
def OnGetItemColumnImage(self, item, col):
return []
def OnGetItemImage(self, item):
return []
def OnGetItemAttr(self, item):
return None
def OnGetItemTextColour(self, item, col):
return None
def OnGetItemToolTip(self, item, col):
return None
def OnGetItemKind(self, item):
return 1
def OnGetItemColumnKind(self, item, col):
if col==0:
return self.OnGetItemKind(item)
return 0
def OnGetItemColumnCheck(self, item, column):
item_column = (item, column)
if item_column in self.checked:
return True
else:
return False
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()

Related

Filtering a QTableview

please have a look at the following code:
import timeit
from builtins import super
import pandas as pd
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import QBrush
from PyQt5.uic import loadUi
class PandasTableModel(QtGui.QStandardItemModel):
def __init__(self, data, parent=None):
QtGui.QStandardItemModel.__init__(self, parent)
self._data = data
for col in data.columns:
data_col = [QtGui.QStandardItem("{}".format(x)) for x in data[col].values]
self.appendColumn(data_col)
return
def rowCount(self, parent=None):
return len(self._data.values)
def columnCount(self, parent=None):
return self._data.columns.size
def headerData(self, x, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[x]
if orientation == Qt.Vertical and role == Qt.DisplayRole:
return self._data.index[x]
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
return super().flags(index) | Qt.ItemIsEditable # add editable flag.
def setData(self, index, value, role):
if role == Qt.EditRole:
# Set the value into the frame.
self._data.iloc[index.row(), index.column()] = value
return True
return False
class TableViewer(QtWidgets.QMainWindow):
def __init__(self):
super(TableViewer, self).__init__()
self.ui = loadUi("QTableViewForm.ui", self)
self.ui.cmdRun1.clicked.connect(self.RunFunction1)
self.ui.cmdRun2.clicked.connect(self.RunFunction2)
self.ui.inputFilter.textChanged.connect(self.SetFilteredView)
self.showdata()
def showdata(self):
start = timeit.default_timer()
print("Start LoadData")
data = pd.read_pickle("productdata.pkl")
self.model = PandasTableModel(data)
self.ui.tableData.setModel(self.model)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(-1) # Search all columns.
self.proxy_model.setSourceModel(self.model)
self.proxy_model.sort(0, Qt.AscendingOrder)
self.proxy_model.setFilterCaseSensitivity(False)
self.ui.tableData.setModel(self.proxy_model)
print("Stop LoadData")
end = timeit.default_timer()
print("Process Time: ", (end - start))
def RunFunction1(self):
start = timeit.default_timer()
print("Start RunFunction1")
#Achtergrondkleur van de cel of rij is aanpasbaar:
item=self.model.item(2,2)
item.setBackground(QBrush(Qt.red))
print("Stop RunFunction1")
end = timeit.default_timer()
print("Process Time: ", (end - start))
def RunFunction2(self):
start = timeit.default_timer()
print("Start RunFunction1")
for i in range(10):
item = self.model.item(2, i)
item.setBackground(QBrush(Qt.green))
print("Stop RunFunction1")
end = timeit.default_timer()
print("Process Time: ", (end - start))
def SetFilteredView(self):
# print("Start set_filter")
filter_text = self.ui.inputFilter.text()
self.proxy_model.setFilterFixedString(filter_text)
filter_result = self.proxy_model.rowCount()
self.ui.lblResult.setText("(" + str(filter_result) + " records)")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = TableViewer()
win.show()
sys.exit(app.exec_())
I have a window with a filter input field. The filtering works great and really fast. Now i would like to be able to enter more than 1 entry to narrow the search. (Basicly an "AND" function in the filterstring).
Any suggestions how to do this ?
Cheers Johnson

wxPython dialogs: How to validate inputs in multiple controls?

I have a wx.Dialog with several input fields. When the OK button is pressed I want to run validations such as:
If one of three fields is filled in, all three must be filled in.
If a radiobutton is set, then it's corresponding field must not be empty.
I know about the normal validators that get attached to a control with wx.Window.SetValidator(). But these just validate the content of their respective control.
I tried attaching a validator to the wx.Dialog, but this is not called unfortunately.
I tried binding the event from the ID_OK button to a handler to do the validation there, but the result is that the dialog doesn't close anymore.
What is the proper way to do this kind of validation?
Below is my code with what I tried:
import wx
class DialogValidator(wx.Validator):
def Clone(self):
return DialogValidator()
def Validate(self, win):
print("this method is never called :-(")
field1 = win.field1.GetValue()
field2 = win.field2.GetValue()
field3 = win.field3.GetValue()
if len(field1) > 0 or len(field2) > 0 or len(field3) > 0:
# if one of these is filled in, all three must
if len(field1) == 0 or len(field2) == 0 or len(field3) == 0:
wx.MessageBox("All three fields must be filled in!", "Error")
return False
return True
def TransferToWindow(self):
return True
def TransferFromWindow(self):
return True
class MyDialog(wx.Dialog):
def __init__(self, *args, **kwds):
wx.Dialog.__init__(self, *args, **kwds)
self.field1 = wx.TextCtrl(self, wx.ID_ANY, "")
self.field2 = wx.TextCtrl(self, wx.ID_ANY, "")
self.field3 = wx.TextCtrl(self, wx.ID_ANY, "")
self.radio1 = wx.RadioButton(self, wx.ID_ANY, "radio1", style=wx.RB_GROUP)
self.radio2 = wx.RadioButton(self, wx.ID_ANY, "radio2")
self.dialog_btn_sizer = wx.StdDialogButtonSizer()
self.dialog_btn_sizer.AddButton(wx.Button(self, wx.ID_OK))
self.dialog_btn_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
self.dialog_btn_sizer.Realize()
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.radio1)
main_sizer.Add(self.radio2)
main_sizer.Add(self.field1)
main_sizer.Add(self.field2)
main_sizer.Add(self.field3)
main_sizer.Add(self.dialog_btn_sizer, 0, wx.EXPAND, 0)
self.SetSizer(main_sizer)
main_sizer.Fit(self)
self.Layout()
self.SetValidator(DialogValidator()) # doesn't work unfortunately
self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK) # doesn't work either
def on_ok(self, event):
field1 = self.field1.GetValue()
field2 = self.field2.GetValue()
field3 = self.field3.GetValue()
if len(field1) > 0 or len(field2) > 0 or len(field3) > 0:
# if one of these is filled in, all three must
if len(field1) == 0 or len(field2) == 0 or len(field3) == 0:
wx.MessageBox("All three fields must be filled in!", "Error")
event.Skip()
return
# Note that I do NOT call event.Skip() here!
# I was hoping the original handler would pick up the event
# and properly close the dialog -> unfortunately this is not the case
print("inputs fine, now the dialog should get closed")
I now solved it by setting a validator on one field that accesses the other fields by going over the dialog. I found out that win in wx.Validator.Validate(self, win) refers to the underlying wx.Dialog.
(Why handling the ID_OK button didn't work I don't know, but this would be another question.)
I'm posting my solution in the hope it helps others struggling with validators:
import wx
class FieldValidator(wx.Validator):
def Clone(self):
return FieldValidator()
def Validate(self, win):
# 'win' refers to the dialog,
# so I can access the other controls like this:
field1 = win.field1.GetValue()
field2 = win.field2.GetValue()
field3 = win.field3.GetValue()
# btw: with self.GetWindow() I can get the control being validated
if len(field1) > 0 or len(field2) > 0 or len(field3) > 0:
# if one of these is filled in, all three must
if len(field1) == 0 or len(field2) == 0 or len(field3) == 0:
wx.MessageBox("All three fields must be filled in!", "Error")
return False
return True
def TransferToWindow(self):
return True
def TransferFromWindow(self):
return True
class MyDialog(wx.Dialog):
def __init__(self, *args, **kwds):
wx.Dialog.__init__(self, *args, **kwds)
self.field1 = wx.TextCtrl(self, wx.ID_ANY, "")
self.field2 = wx.TextCtrl(self, wx.ID_ANY, "")
self.field3 = wx.TextCtrl(self, wx.ID_ANY, "")
self.radio1 = wx.RadioButton(self, wx.ID_ANY, "radio1", style=wx.RB_GROUP)
self.radio2 = wx.RadioButton(self, wx.ID_ANY, "radio2")
self.dialog_btn_sizer = wx.StdDialogButtonSizer()
self.dialog_btn_sizer.AddButton(wx.Button(self, wx.ID_OK))
self.dialog_btn_sizer.AddButton(wx.Button(self, wx.ID_CANCEL))
self.dialog_btn_sizer.Realize()
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.radio1)
main_sizer.Add(self.radio2)
main_sizer.Add(self.field1)
main_sizer.Add(self.field2)
main_sizer.Add(self.field3)
main_sizer.Add(self.dialog_btn_sizer, 0, wx.EXPAND, 0)
self.SetSizer(main_sizer)
main_sizer.Fit(self)
self.Layout()
self.field1.SetValidator(FieldValidator())

wxpython GridBagSizer inside ScrolledWindow not working

Good evening/morning,
I have been working on a program and have realized that on lower resolutions, most of the program gets cut off. To counteract this, I decided to add a scrolledwindow to the GUI. Looking around, I found this example:
import wx as wx
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
#Panel that holds all the other panels
self.mainPanel = wx.ScrolledWindow(self, -1)
self.mainPanel.SetScrollbars(1, 1, 1, 1)
self.mainPanel.SetBackgroundColour("LIGHT GREY")
#Panels in mainPanel
self.lefttopPanel = wx.Panel(self.mainPanel, -1)
self.lefttopPanel.SetBackgroundColour("WHITE")
self.sizewidgets(self.panelwidgets(
text='Left Top Panel',
num=12,
parent=self.lefttopPanel),
parent=self.lefttopPanel)
self.leftmiddlePanel = wx.Panel(self.mainPanel, -1)
self.leftmiddlePanel.SetBackgroundColour("WHITE")
self.sizewidgets(self.panelwidgets(
text='Left Middle Panel',
num=6,
parent=self.leftmiddlePanel),
parent=self.leftmiddlePanel)
self.leftbottomPanel = wx.Panel(self.mainPanel, -1)
self.leftbottomPanel.SetBackgroundColour("WHITE")
self.sizewidgets(self.panelwidgets(
text='Left Bottom Panel',
num=8,
parent=self.leftbottomPanel),
parent=self.leftbottomPanel)
self.righttopPanel = wx.Panel(self.mainPanel, -1)
self.righttopPanel.SetBackgroundColour("WHITE")
self.sizewidgets(self.panelwidgets(
text='Right Top Panel',
num=8,
parent=self.righttopPanel),
parent=self.righttopPanel)
self.rightbottomPanel = wx.Panel(self.mainPanel, -1)
self.rightbottomPanel.SetBackgroundColour("WHITE")
self.sizewidgets(self.panelwidgets(
text='Right Bottom Panel',
num=8,
parent=self.rightbottomPanel),
parent=self.rightbottomPanel)
mpsizer = wx.GridBagSizer(vgap=4, hgap=4)
mpsizer.Add(self.lefttopPanel, pos=(0,0), span=(1,1), flag=wx.EXPAND)
mpsizer.Add(self.leftmiddlePanel, pos=(1,0), span=(1,1), flag=wx.EXPAND)
mpsizer.Add(self.leftbottomPanel, pos=(2,0), span=(1,2), flag=wx.EXPAND)
mpsizer.Add(self.righttopPanel, pos=(0,1), span=(2,2), flag=wx.EXPAND)
mpsizer.Add(self.rightbottomPanel, pos=(2,2), span=(1,1), flag=wx.EXPAND)
mpsizer.AddGrowableCol(1)
mpsizer.AddGrowableRow(1)
self.mainPanel.SetSizer(mpsizer)
#Adding a refresh to resize event
self.Bind(wx.EVT_SIZE, self.OnResize)
self.Show()
def OnResize(self, event):
self.Refresh()
event.Skip()
def sizewidgets(self, widgetlist , parent):
psizer = wx.GridSizer(cols=2, vgap=5,hgap=5)
for widget in widgetlist:
psizer.Add(widget)
parent.SetSizer(psizer)
def panelwidgets(self, text, num, parent):
widgets = []
for i in range(num):
widgets += [wx.StaticText(parent, label=text)]
return widgets
if __name__ == "__main__":
app = wx.App()
MainFrame(None, size=(800, 800), title="GridBagSizer Problem")
app.MainLoop()
That example worked great, so I decided to try to modify it to work with my program, since I have not had luck in just adding a scrolledwindow to my whole program, so I made these changes:
import wx as wx
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
#Panel that holds all the other panels
self.mainPanel = wx.ScrolledWindow(self, -1)
self.mainPanel.SetScrollbars(1, 1, 1, 1)
self.mainPanel.SetBackgroundColour("LIGHT GREY")
# Handles the c reation of all of the static text labels
def make_label(text, starty, startx, height, width):
self.label = wx.StaticText(self, -1, text, wx.DefaultPosition, wx.DefaultSize)
sizer.Add(self.label, (starty, startx), (height, width), wx.EXPAND)
return self.label
sizer = wx.GridBagSizer()
for i in range(0, 4):
# all the labels
make_label('Voltage', 7, i*5, 1, 1)
make_label('Current', 8, i*5, 1, 1)
make_label('Power', 9, i*5, 1, 1)
make_label('Rail 1', 6, (i*5)+1, 1, 1)
make_label('Rail 2', 6, (i*5)+2, 1, 1)
make_label('Rail 3', 6, (i*5)+3, 1, 1)
make_label('Total Power', 6, (i*5)+4, 1, 1)
make_label('Status:', 14, (i*5), 1, 1)
sizer.AddGrowableRow(1)
sizer.AddGrowableCol(1)
self.mainPanel.SetSizer(sizer)
#Adding a refresh to resize event
self.Bind(wx.EVT_SIZE, self.OnResize)
self.Show()
def OnResize(self, event):
self.Refresh()
event.Skip()
def sizewidgets(self, widgetlist , parent):
psizer = wx.GridSizer(cols=2, vgap=5,hgap=5)
for widget in widgetlist:
psizer.Add(widget)
parent.SetSizer(psizer)
def panelwidgets(self, text, num, parent):
widgets = []
for i in range(num):
widgets += [wx.StaticText(parent, label=text)]
return widgets
if __name__ == "__main__":
app = wx.App()
MainFrame(None, size=(800, 800), title="GridBagSizer Problem")
app.MainLoop()
For some reason, with the changes I make however, the scrollbar just sits in the top left corner and is not functional. The same thing happens when I try to add it to my larger program as well. Can anyone help me out with why this is happening?
The child widgets that are created by
self.label = wx.StaticText(self, -1, text, wx.DefaultPosition, wx.DefaultSize)
have self as the parent, which is the MainFrame class.
Change it so that the parent is the instance of scrolled window as follows
self.label = wx.StaticText(self.mainPanel, -1, text, wx.DefaultPosition, wx.DefaultSize)

wxPython ImageViewer

I am to create a simple image viewer using wxPython. I am using a panel and a button upon which by clicking a picture would be shown from a directory (Images). Below is the code. I have taken pieces from different websites.
My question is if I maximize the window, the picture viewer as well as the button remain same. I want the picture viewer to increase as well.
Another thing is, after I press the "Load" button, it moves to the left. How do I fix these two problems?
I understand for those 2 above questions, this so big program might be unnecessary. I am a newbie to Python.
Thanks in advance.
import wx, os
class Panel1(wx.Panel):
def __init__(self, *args, **kwds):
wx.Panel.__init__(self, *args, **kwds)
self.jpgs = self.GetJpgList("./Images")
self.CurrentJpg = 0
self.MaxImageSize = 200
self.img = wx.EmptyImage(self.MaxImageSize,self.MaxImageSize)
self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY, wx.BitmapFromImage(self.img))
self.button_1 = wx.Button(self, wx.ID_ANY, "Load")
self.Bind(wx.EVT_BUTTON, self.OnLoad, self.button_1)
self.__do_layout()
self.Layout()
def GetJpgList(self, dir):
jpgs = [f for f in os.listdir(dir) if f[-4:] == ".JPG"]
return [os.path.join(dir, f) for f in jpgs]
def __do_layout(self):
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add((1,1),1)
sizer_1.Add(self.imageCtrl, 0, wx.ALL|wx.EXPAND, 5)
sizer_1.Add((1,1),1)
sizer_1.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.ALL|wx.EXPAND, 5)
sizer_2.Add(self.button_1, 0, wx.ALL|wx.TOP, 5)
sizer_1.Add(sizer_2, 0, wx.ALL, 5)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
def OnNext(self, event):
path = self.jpgs[self.CurrentJpg]
self.Img = wx.Image(path, wx.BITMAP_TYPE_ANY)
W = self.Img.GetWidth()
H = self.Img.GetHeight()
if W > H:
NewW = self.MaxImageSize
NewH = self.MaxImageSize * H / W
else:
NewH = self.MaxImageSize
NewW = self.MaxImageSize * W / H
self.Img = self.Img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(self.Img))
#self.Fit()
#self.Layout()
self.Refresh()
self.CurrentJpg += 1
if self.CurrentJpg > len(self.jpgs) -1:
self.CurrentJpg = 0
# end of class Panel1
class Frame1(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.__set_properties()
self.__do_layout()
def __set_properties(self):
self.SetTitle("Picture")
def __do_layout(self):
panel1 = Panel1(self)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(panel1, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.sizer.Fit(self)
self.Layout()
self.Centre()
# end of class Frame1
class Game1(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame1 = Frame1(None, wx.ID_ANY, "")
self.SetTopWindow(frame1)
frame1.Show()
return 1
# end of class Game1
if __name__ == "__main__":
game1 = Game1(0)
game1.MainLoop()
To make the image resize itself on maximize, you will need to catch wx.EVT_MAXIMIZE. Then in your event handler, you'll need to call your function that updates the image control you created. You may need to use the Frame's size to help you determine what your MaxImageSize should be.
For the load button issue, you may need to call the sizer or the widget's parent's Layout() method.

how to delegate on specific postion(row, col) in PyQt QTreeView

my purpose is to display an different icon in treeview list when a specific postion(row, column) match a value. eg: (row, 2) is dir or file, will diplay different icon. because this is not used in local filesystem, QDir or Qfilesystem model not suite for this.
i know a bit on MVC that controller display in view, make model as data interface api. but i do not how to make it works on specific position(row ,col) as my expect.
i have tried to add args in ImageDelegate(like pass icon file name to it), but failed maybe due to its parent Class not accept more args.
hope someone can give me some light.
class ImageDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
QtGui.QStyledItemDelegate.__init__(self, parent)
#self.icon =icon
def paint(self, painter, option, index):
#painter.fillRect(option.rect, QtGui.QColor(191,222,185))
# path = "path\to\my\image.jpg"
path = "icon1.png"
image = QtGui.QImage(str(path))
pixmap = QtGui.QPixmap.fromImage(image)
pixmap.scaled(16, 16, QtCore.Qt.KeepAspectRatio)
painter.drawPixmap(option.rect.x(), option.rect.y(), pixmap)
and i can use this delegate in my view. but it will change all line in specific column.
def init_remotetreeview(self):
self.model = myModel(self.remote_Treeview)
for therow in range(self.model.rowCount(QModelIndex())) :
print self.model.data(self.model.index(therow, 2, QtCore.QModelIndex()),Qt.DisplayRole).toString() # i do check the value will used to load correct icon.
self.remote_Treeview.setItemDelegate(ImageDelegate(self)) # this change all lines
self.remote_Treeview.setModel(self.model)
In fact, your have some light in your code, isn't it ? (Just kidding.)
Your have right way to use QtGui.QStyledItemDelegate. I have reference how to implement it (But, C++ only). 'Star Delegate Example', 'QItemDelegate Class Reference C++' and 'QItemDelegate Class Reference PyQt4';
Keyword : Your have to implement paint draw your element what your want (I think this is your want.)
Little example, Hope is help;
import sys
from PyQt4 import QtCore, QtGui
from functools import partial
class QCustomDelegate (QtGui.QItemDelegate):
signalNewPath = QtCore.pyqtSignal(object)
def createEditor (self, parentQWidget, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
editorQWidget = QtGui.QPushButton(parentQWidget)
editorQWidget.released.connect(partial(self.requestNewPath, indexQModelIndex))
return editorQWidget
else:
return QtGui.QItemDelegate.createEditor(self, parentQWidget, optionQStyleOptionViewItem, indexQModelIndex)
def setEditorData (self, editorQWidget, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = indexQModelIndex.model().data(indexQModelIndex, QtCore.Qt.EditRole).toString()
editorQWidget.setText(textQString)
else:
QtGui.QItemDelegate.setEditorData(self, editorQWidget, indexQModelIndex)
def setModelData (self, editorQWidget, modelQAbstractItemModel, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = editorQWidget.text()
modelQAbstractItemModel.setData(indexQModelIndex, textQString, QtCore.Qt.EditRole)
else:
QtGui.QItemDelegate.setModelData(self, editorQWidget, modelQAbstractItemModel, indexQModelIndex)
def updateEditorGeometry(self, editorQWidget, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
editorQWidget.setGeometry(optionQStyleOptionViewItem.rect)
else:
QtGui.QItemDelegate.updateEditorGeometry(self, editorQWidget, optionQStyleOptionViewItem, indexQModelIndex)
def requestNewPath (self, indexQModelIndex):
self.signalNewPath.emit(indexQModelIndex)
def paint (self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = indexQModelIndex.model().data(indexQModelIndex, QtCore.Qt.EditRole).toString()
painterQPainter.drawPixmap (
optionQStyleOptionViewItem.rect.x(),
optionQStyleOptionViewItem.rect.y(),
QtGui.QPixmap(textQString).scaled(180, 180, QtCore.Qt.KeepAspectRatio))
else:
QtGui.QItemDelegate.paint(self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class QCustomTreeWidget (QtGui.QTreeWidget):
def __init__(self, parent = None):
super(QCustomTreeWidget, self).__init__(parent)
self.setColumnCount(1)
myQCustomDelegate = QCustomDelegate()
self.setItemDelegate(myQCustomDelegate)
myQCustomDelegate.signalNewPath.connect(self.getNewPath)
def addMenu (self, path, parentQTreeWidgetItem = None):
if parentQTreeWidgetItem == None:
parentQTreeWidgetItem = self.invisibleRootItem()
currentQTreeWidgetItem = QtGui.QTreeWidgetItem(parentQTreeWidgetItem)
currentQTreeWidgetItem.setData(0, QtCore.Qt.EditRole, path)
currentQTreeWidgetItem.setFlags(currentQTreeWidgetItem.flags() | QtCore.Qt.ItemIsEditable)
for i in range(self.columnCount()):
currentQSize = currentQTreeWidgetItem.sizeHint(i)
currentQTreeWidgetItem.setSizeHint(i, QtCore.QSize(currentQSize.width(), currentQSize.height() + 200))
def getNewPath (self, indexQModelIndex):
currentQTreeWidgetItem = self.itemFromIndex(indexQModelIndex)
pathQStringList = QtGui.QFileDialog.getOpenFileNames()
if pathQStringList.count() > 0:
textQString = pathQStringList.first()
currentQTreeWidgetItem.setData(indexQModelIndex.column(), QtCore.Qt.EditRole, textQString)
print textQString
class QCustomQWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomQWidget, self).__init__(parent)
self.myQCustomTreeWidget = QCustomTreeWidget(self)
self.allQHBoxLayout = QtGui.QHBoxLayout()
self.allQHBoxLayout.addWidget(self.myQCustomTreeWidget)
self.setLayout(self.allQHBoxLayout)
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter01.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter02.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter04.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter05.jpg''')
app = QtGui.QApplication([])
myQCustomQWidget = QCustomQWidget()
myQCustomQWidget.show()
sys.exit(app.exec_())
Note: In same way to implement QTreeView, but different is set values only.
If your want to show image by path in some index (In this case : 2nd). Your can find it by using QModelIndex QAbstractItemModel.index (self, int row, int column, QModelIndex parent = QModelIndex()), And do want your want.
Example;
import sys
from PyQt4 import QtCore, QtGui
class QCustomDelegate (QtGui.QItemDelegate):
def paint (self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 3:
currentQAbstractItemModel = indexQModelIndex.model()
iconQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 1, indexQModelIndex.parent())
pathQString = currentQAbstractItemModel.data(iconQModelIndex, QtCore.Qt.EditRole).toString()
iconQPixmap = QtGui.QPixmap(pathQString)
if not iconQPixmap.isNull():
painterQPainter.drawPixmap (
optionQStyleOptionViewItem.rect.x(),
optionQStyleOptionViewItem.rect.y(),
iconQPixmap.scaled(20, 20, QtCore.Qt.KeepAspectRatio))
else:
QtGui.QItemDelegate.paint(self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex)
myQApplication = QtGui.QApplication([])
myQTreeView = QtGui.QTreeView()
headerQStandardItemModel = QtGui.QStandardItemModel()
headerQStandardItemModel.setHorizontalHeaderLabels([''] * 4)
myQTreeView.setModel(headerQStandardItemModel)
# Set delegate
myQCustomDelegate = QCustomDelegate()
myQTreeView.setItemDelegate(myQCustomDelegate)
# Append data row 1
row1QStandardItem = QtGui.QStandardItem('ROW 1')
row1QStandardItem.appendRow([QtGui.QStandardItem(''), QtGui.QStandardItem('1.jpg'), QtGui.QStandardItem(''), QtGui.QStandardItem('')])
headerQStandardItemModel.appendRow(row1QStandardItem)
# Append data row 2
row2QStandardItem = QtGui.QStandardItem('ROW 2')
row2QStandardItem.appendRow([QtGui.QStandardItem(''), QtGui.QStandardItem('2.png'), QtGui.QStandardItem(''), QtGui.QStandardItem('')])
headerQStandardItemModel.appendRow(row2QStandardItem)
myQTreeView.show()
sys.exit(myQApplication.exec_())
experimental result:
Note: I have image 1.jpg, 2.png.

Resources