wxPython wxListCtrl selected row color - colors
I want to have certain rows selected color be red instead of the standard color (blue on windows) so that I can indicate status. Anyone know if this is possible in wxPython?
In your class that you derive from wx.ListCtrl, take a look at overriding
def OnGetItemAttr(self, item):
return self.normalAttr[item % 2]
#
Where the item attributes are initialized ahead of time using:
self.normalAttr = []
self.normalAttr.append(wx.ListItemAttr())
grayAttr = wx.ListItemAttr()
grayAttr.SetBackgroundColour(lightGray)
self.normalAttr.append(grayAttr)
So in this case, I'm alternating background colors between the default, and a light Gray attribute.
This function is called for each row as its painted, so you can use it to indicate all sorts of status. If row is selected should be an easy case.
In order to do what you want, i.e. have a different selection color when certain items are selected, you will need to drop into win32. Fortunately, it is not too hard to do that in python. It does however make your code platform dependent. I tried it out today in a small program. If the Genre is not "Rock" I make the selection orange. Here are some screenshots.
Rock Items Selected
Mixed Items Selected. Notice the RnB and Blues are selected with Orange
alt text http://img258.imageshack.us/img258/1307/soshot2.jpg
Here is the code. It looks scary at first but if you know any win32, its not that bad. I make use of the pywin32 package and the std ctypes libraries. I had to define some of the SDK constants as they were not available in the win32con module.
import sys
import wx
import wx.lib.mixins.listctrl as listmix
import win32api
import win32gui
import win32con
import win32gui_struct
import commctrl
import ctypes
from ctypes.wintypes import BOOL, HWND, RECT, UINT, DWORD, HDC, DWORD, LPARAM, COLORREF
LVM_FIRST = 0x1000
LVM_GETSUBITEMRECT=(LVM_FIRST + 56)
LVIR_BOUNDS =0
LVIR_ICON =1
LVIR_LABEL =2
LVIR_SELECTBOUNDS =3
DEFAULT_GUI_FONT =17
#LPNMHDR
class NMHDR(ctypes.Structure):
pass
INT = ctypes.c_int
NMHDR._fields_ = [('hwndFrom', HWND), ('idFrom', UINT), ('code', INT)]
LPNMHDR = ctypes.POINTER(NMHDR)
#LPNMCUSTOMDRAW
class NMCUSTOMDRAW(ctypes.Structure):
pass
NMCUSTOMDRAW._fields_ = [('hdr', NMHDR), ('dwDrawStage', DWORD), ('hdc', ctypes.c_int),
('rc', RECT), ('dwItemSpec', DWORD), ('uItemState', UINT),
('lItemlParam', LPARAM)]
LPNMCUSTOMDRAW = ctypes.POINTER(NMCUSTOMDRAW)
#LPNMLVCUSTOMDRAW
class NMLVCUSTOMDRAW(ctypes.Structure):
pass
NMLVCUSTOMDRAW._fields_ = [('nmcd', NMCUSTOMDRAW),
('clrText', COLORREF),
('clrTextBk', COLORREF),
('iSubItem', ctypes.c_int),
('dwItemType', DWORD),
('clrFace', COLORREF),
('iIconEffect', ctypes.c_int),
('iIconPhase', ctypes.c_int),
('iPartId', ctypes.c_int),
('iStateId', ctypes.c_int),
('rcText', RECT),
('uAlign', UINT)
]
LPNMLVCUSTOMDRAW = ctypes.POINTER(NMLVCUSTOMDRAW)
musicdata = {
1 : ("Bad English", "The Price Of Love", "Rock"),
2 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
3 : ("George Michael", "Praying For Time", "Rock"),
4 : ("Gloria Estefan", "Here We Are", "Rock"),
5 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
6 : ("Michael Bolton", "How Am I Supposed To Live Without You", "Blues"),
7 : ("Paul Young", "Oh Girl", "Rock"),
8 : ("Paula Abdul", "Opposites Attract", "Rock"),
9 : ("Richard Marx", "Should've Known Better", "Rock"),
10 : ("Bobby Brown", "My Prerogative", "RnB"),
}
class MyListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
def __init__(self, parent, ID, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
def ShouldCustomDraw(self, row):
if self.IsSelected(row):
listitem = self.GetItem(row, 2)
genre = listitem.GetText()
return genre != "Rock"
def CustomDraw(self, lpcd):
if lpcd.contents.nmcd.dwDrawStage == commctrl.CDDS_PREPAINT:
return (True, commctrl.CDRF_NOTIFYITEMDRAW)
if lpcd.contents.nmcd.dwDrawStage == commctrl.CDDS_ITEMPREPAINT:
if self.ShouldCustomDraw(lpcd.contents.nmcd.dwItemSpec):
#do custom drawing for non Rock selected rows
#paint the selection background
color = win32api.RGB(255, 127, 0) #orange
brush = win32gui.CreateSolidBrush(color)
r = lpcd.contents.nmcd.rc
win32gui.FillRect(int(lpcd.contents.nmcd.hdc), (r.left+4, r.top, r.right, r.bottom), brush)
win32gui.DeleteObject(brush)
return (True, commctrl.CDRF_NOTIFYSUBITEMDRAW)
if lpcd.contents.nmcd.dwDrawStage == commctrl.CDDS_ITEMPREPAINT|commctrl.CDDS_SUBITEM:
row = lpcd.contents.nmcd.dwItemSpec
col = lpcd.contents.iSubItem
item = self.GetItem(row, col)
text = item.GetText()
#paint the text
rc = RECT()
rc.top = col
if col > 0:
rc.left = LVIR_BOUNDS
else:
rc.left = LVIR_LABEL
success = win32api.SendMessage(self.Handle, LVM_GETSUBITEMRECT, row, ctypes.addressof(rc))
if col > 0:
rc.left += 5
else:
rc.left += 2
rc.top += 2
if success:
oldColor = win32gui.SetTextColor(lpcd.contents.nmcd.hdc, win32gui.GetSysColor(win32con.COLOR_HIGHLIGHTTEXT))
win32gui.DrawText(lpcd.contents.nmcd.hdc, text, len(text), (rc.left, rc.top, rc.right, rc.bottom), win32con.DT_LEFT|win32con.DT_VCENTER)
win32gui.SetTextColor(lpcd.contents.nmcd.hdc, oldColor)
return (True, commctrl.CDRF_SKIPDEFAULT)
# don't need custom drawing
return (True, commctrl.CDRF_DODEFAULT)
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self._sizer = wx.BoxSizer(wx.VERTICAL)
tID = wx.NewId()
self._ctl = MyListCtrl(self, tID,
style=wx.LC_REPORT
#| wx.BORDER_SUNKEN
| wx.BORDER_NONE
| wx.LC_EDIT_LABELS
| wx.LC_SORT_ASCENDING
#| wx.LC_NO_HEADER
#| wx.LC_VRULES
#| wx.LC_HRULES
#| wx.LC_SINGLE_SEL
)
self._sizer.Add(self._ctl, 1, wx.EXPAND, 3)
self.PopulateList()
self.oldWndProc = win32gui.SetWindowLong(self.GetHandle(), win32con.GWL_WNDPROC, self.MyWndProc)
def MyWndProc(self, hWnd, msg, wParam, lParam):
if msg == win32con.WM_NOTIFY:
hwndFrom, idFrom, code = win32gui_struct.UnpackWMNOTIFY(lParam)
if code == commctrl.NM_CUSTOMDRAW and hwndFrom == self._ctl.Handle:
lpcd = ctypes.cast(lParam, LPNMLVCUSTOMDRAW)
retProc, retCode = self._ctl.CustomDraw(lpcd)
if retProc:
return retCode
# Restore the old WndProc. Notice the use of wxin32api
# instead of win32gui here. This is to avoid an error due to
# not passing a callable object.
if msg == win32con.WM_DESTROY:
win32api.SetWindowLong(self.GetHandle(),
win32con.GWL_WNDPROC,
self.oldWndProc)
# Pass all messages (in this case, yours may be different) on
# to the original WndProc
return win32gui.CallWindowProc(self.oldWndProc,
hWnd, msg, wParam, lParam)
def PopulateList(self):
self._ctl.InsertColumn(0, "Artist")
self._ctl.InsertColumn(1, "Title")
self._ctl.InsertColumn(2, "Genre")
items = musicdata.items()
for key, data in items:
index = self._ctl.InsertStringItem(sys.maxint, data[0])
self._ctl.SetStringItem(index, 1, data[1])
self._ctl.SetStringItem(index, 2, data[2])
self._ctl.SetItemData(index, key)
self._ctl.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self._ctl.SetColumnWidth(1, wx.LIST_AUTOSIZE)
self._ctl.SetColumnWidth(2, 100)
self.currentItem = 0
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'wxListCtrl StackOverflow')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
Related
Write class such that calling instance returns all instance variables
I have answered my own question - see answer below I'm writing a class, and I want this behavior: a = f(10,20) some_funct(a.row) # some_function is given 10 some_funct(a.col) # some_function is given 20 some_funct(a) # some_function is given a tuple of 10, 20 <-- THIS ONE :) The last behavior is stumping me. I have not seen any examples that cover this. Thus far: class f(object): """Simple 2d object""" row: int col: int def __init__(self, row, col): self.row = row self.col = col Explictly I do not want another method, say, self.both = row, col. I just want to 'call' the instance I'm new to classes, so any improvements are welcome. Properties, setters, getters etc. EDIT 1: Replaced "print" with "some_function" in the question, and modified title
You can do like this class f(object): """Simple 2d object""" row: int col: int def __init__(self, row, col): self.row = row self.col = col def __str__(self): return f"row = {row}, col = {col}" and print like this a = f(10,20) print(a) # row = 10, col = 20
This might help class f(object): """Simple 2d object""" row: int col: int def __init__(self, row, col): self.row = row self.col = col def some_funct(self): return (self.row, self.col) You can access like a = f(10,20) a.some_funct() # (10, 20) # or row, col = a.some_funct()
From python 3.7 dataclasses have been introduced and their goal is to create classes that mainly contains data. Dataclasses comes with some helper function that extract the class attributes dict/tuples. e.g. from dataclasses import dataclass,asdict,astuple #dataclass class f: x: int y: int f_instance = f(10,20) asdict(f_instance) # --> {'x': 10, 'y': 20} astuple(f_instance) # -> (10,20) EDIT I : Another technique would be to use namedtuple e.g.: from collections import namedtuple f = namedtuple('p',['row','col']) a =f(10,20) a.row #-> 10 a.col #-> 20
class f(tuple): """Simple 2d object""" def __new__(cls, x, y): return tuple.__new__(f, (x, y)) def __init__(self, x, y): self.col = x self.row = y foo = f(1,2) print(foo.col) >>>1 print(foo.row) >>>2 print(foo) >>>(1, 2) Importantly: If you want it to behave like a tuple then make it a subclass of tuple. Much stuffing around but stumbled upon an external site which gave me clues about the keywords to search on here. The SO question is here but I have modified that answer slightly. I'm still a little confused because the other site says to use new in the init as well but does not give a clear example.
How to find all records(form list) from given time range in Python?
I have to have an input function which allowes me to find specyfic records (form list created before) in time range like f.ex : album range? Input-> between 3 - 5 hours, -> two album was found in ouput
This is an code that represent your question, feel free to ask question about it: class Album: release_time: int def __init__(self, release_time): self.release_time = release_time def __repr__(self): return f"Album({self.release_time})" if __name__ == '__main__': album_list = [Album(i) for i in range(10)] user_input_min, user_input_max = (int(input('min?')), int(input('max?'))) if user_input_min > user_input_max: print("error") else: album_nbr = 0 result_list = list() for album in album_list: if user_input_min <= album.release_time <= user_input_max: album_nbr += 1 result_list.append(album) print(album_list) print(f'{album_nbr} was found') print(result_list) Output: min?3 max?8 [Album(0), Album(1), Album(2), Album(3), Album(4), Album(5), Album(6), Album(7), Album(8), Album(9)] 6 was found [Album(3), Album(4), Album(5), Album(6), Album(7), Album(8)]
Dictionary with functions versus dictionary with class
I'm creating a game where i have the data imported from a database, but i have a little problem... Currently i get a copy of the data as a dictionary, which i need to pass as argument to my GUI, however i also need to process some data, like in this example: I get the data as a dict (I've created the UseDatabase context manager and is working): def get_user(name: str, passwd: str): user = {} user['name'] = name user['passwd'] = passwd with UseDatabase() as cursor: _SQL = "SELECT id, cash, ruby FROM user WHERE name='Admin' AND password='adminpass'" cursor.execute(_SQL) res = cursor.fetchall() if res: user['id'] = res[0][0] user['cash'] = res[0][1] user['ruby'] = res[0][2] return user return res . . . def get_activities(): with UseDatabase() as cursor: _SQL = "SELECT * FROM activities WHERE user_id='2'" cursor.execute(_SQL) res = cursor.fetchall() if res: ids = [i[0] for i in res] activities = {} for i in res: activities[i[0]] = {'title':i[1],'unlock':i[2],'usr_progress':i[3]} return (ids, activities) return res Need it as a dict in my GUI ("content" argument): class SideBar: def __init__(self, screen: 'pygame.display.set_mode()', box_width: int, box_height: int, content: dict, font: 'font = pygame.font.Font()'): #content dict: {id: {'title':'','unlock':'','usr_progress':''},...} self.box_width = box_width self.box_height = box_height self.box_per_screen = screen.get_height() // box_height self.content = content self.current_box = 1 self.screen = screen self.font = font self.generate_bar() def generate_bar (self): active = [i for i in self.content.keys() if i in range(self.current_box, self.current_box+self.box_per_screen)] for i in range(self.box_per_screen): gfxdraw.box(self.screen,pygame.Rect((0,i*self.box_height),(self.screen.get_width()/3,self.screen.get_height()/3)),(249,0,0,170)) self.screen.blit(self.font.render(str(active[i]) + ' - ' + self.content[active[i]]['title'], True, (255,255,255)),(10,i*self.box_height+4)) for i in range(self.box_per_screen): pygame.draw.rect(self.screen,(50,0,0),pygame.Rect((0,i*self.box_height),(self.screen.get_width()/3,self.screen.get_height()/3)),2) But still need to make some changes in the data: def unlock_act(act_id): if user['cash'] >= activities[act_id]['unlock'] and activities[act_id]['usr_progress'] == 0: user['cash'] -= activities[act_id]['unlock'] activities[act_id]['usr_progress'] = 1 So the question is: in this situation should i keep a copy of the data as dict, and create a class with it plus the methods i need or use functions to edit the data inside the dict?
AttributeError "int" Object Has No Attribute 'txinc'
I keep receiving the error that "int" object has no attribute "txinc." I'm not sure how to fix this. My final output should be total tax. The error is occurring in the tax calculators, specifically with the range function. It should be an integer. import sys class TaxComp: def __init__(self, total_deductions= None, real_estate_deduc= None, exemption_amount= None, taxable_income= None, tax_taxable_income= None, computed_regular_tax= None, inc_tax_before_credits= None, inc_subject_to_tax= None, marginal_tax_base= None, tax_generated= None, AMT= None): self.total_deductions = total_deductions or 'total_deductions' self.real_estate_deduc = real_estate_deduc or 'real_estate_deduc' self.exemption_amount = exemption_amount or 'exemption_amount' self.taxable_income = taxable_income or 'taxable_income' self.tax_taxable_income = tax_taxable_income or 'tax_taxable_income' self.computed_regular_tax = computed_regular_tax or 'computed_regular_tax' self.inc_tax_before_credits = inc_tax_before_credits or 'inc_tax_before_credits' self.inc_subject_to_tax = inc_subject_to_tax or 'inc_subject_to_tax' self.marginal_tax_base = marginal_tax_base or 'marginal_tax_base' self.tax_generated = tax_generated or 'tax_generated' self.AMT = AMT or 'AMT' class TaxableIncome: def taxable_Income(self, AGI): taxable_inc = AGI - 6300 taxable_inc -= 4000 class Totals: txcomp = TaxComp() def __init__(self, taxable_income = txcomp.taxable_income): txinc = TaxableIncome() self.taxable_income = taxable_income or txinc.taxable_Income.taxable_inc class TaxCal: def __init__(self): self.brackets = {(0,8025):0.10, (8025,32550):.15, (32550,78850):.25, (78850, 164550):.28, (164550,357700):.33, (357700,371815):.35, (371815, sys.maxsize):.396} def taxcal (self, tot): tax = 0 for bracket in self.brackets: if tot.txinc.taxable_Income.taxable_inc > bracket[0]: for _ in range(bracket[0], min(int(tot.txinc.taxable_Income.taxable_inc), bracket[1])): tax += self.brackets[bracket] return tax AGI = 100000 txcal = TaxCal() tot = Totals() print(txcal.taxcal(AGI)), format(round(txcal.taxcal(AGI),2))
I guess you would want "txinc = TaxableIncome()" to be "self.txinc = TaxableIncome()" and thereby also the following: "self.txinc.taxable_Income.taxable_inc"
How to list only the visible items in QTableView in pyqt
I have below code to get filters for QTableView. But i am not able to filter multiple column at a time. i.e., if filter column 2 with row 0 col 0 and try to filter column 2, it should show only the visible unique values of column 2 (probably it should show row 0 col 1 only) but now its showing all the elements of column 2 (row 0 col 1, row 1 col 1, row 2 col 1) #-*- coding:utf-8 -*- from PyQt4 import QtCore, QtGui class myWindow(QtGui.QMainWindow): def __init__(self, parent=None): super(myWindow, self).__init__(parent) self.centralwidget = QtGui.QWidget(self) self.lineEdit = QtGui.QLineEdit(self.centralwidget) self.view = QtGui.QTableView(self.centralwidget) self.comboBox = QtGui.QComboBox(self.centralwidget) self.label = QtGui.QLabel(self.centralwidget) self.gridLayout = QtGui.QGridLayout(self.centralwidget) self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1) self.gridLayout.addWidget(self.view, 1, 0, 1, 3) self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1) self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.setCentralWidget(self.centralwidget) self.label.setText("Regex Filter") self.model = QtGui.QStandardItemModel(self) for rowName in range(3) * 5: self.model.invisibleRootItem().appendRow( [ QtGui.QStandardItem("row {0} col {1}".format(rowName, column)) for column in range(3) ] ) self.proxy = QtGui.QSortFilterProxyModel(self) self.proxy.setSourceModel(self.model) self.view.setModel(self.proxy) self.comboBox.addItems(["Column {0}".format(x) for x in range(self.model.columnCount())]) self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged) self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged) self.horizontalHeader = self.view.horizontalHeader() self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked) #QtCore.pyqtSlot(int) def on_view_horizontalHeader_sectionClicked(self, logicalIndex): self.logicalIndex = logicalIndex self.menuValues = QtGui.QMenu(self) self.signalMapper = QtCore.QSignalMapper(self) self.comboBox.blockSignals(True) self.comboBox.setCurrentIndex(self.logicalIndex) self.comboBox.blockSignals(True) valuesUnique = [ self.model.item(row, self.logicalIndex).text() for row in range(self.model.rowCount()) ] actionAll = QtGui.QAction("All", self) actionAll.triggered.connect(self.on_actionAll_triggered) self.menuValues.addAction(actionAll) self.menuValues.addSeparator() for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))): action = QtGui.QAction(actionName, self) self.signalMapper.setMapping(action, actionNumber) action.triggered.connect(self.signalMapper.map) self.menuValues.addAction(action) self.signalMapper.mapped.connect(self.on_signalMapper_mapped) headerPos = self.view.mapToGlobal(self.horizontalHeader.pos()) posY = headerPos.y() + self.horizontalHeader.height() posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex) self.menuValues.exec_(QtCore.QPoint(posX, posY)) #QtCore.pyqtSlot() def on_actionAll_triggered(self): filterColumn = self.logicalIndex filterString = QtCore.QRegExp( "", QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp ) self.proxy.setFilterRegExp(filterString) self.proxy.setFilterKeyColumn(filterColumn) #QtCore.pyqtSlot(int) def on_signalMapper_mapped(self, i): stringAction = self.signalMapper.mapping(i).text() filterColumn = self.logicalIndex filterString = QtCore.QRegExp( stringAction, QtCore.Qt.CaseSensitive, QtCore.QRegExp.FixedString ) self.proxy.setFilterRegExp(filterString) self.proxy.setFilterKeyColumn(filterColumn) #QtCore.pyqtSlot(str) def on_lineEdit_textChanged(self, text): search = QtCore.QRegExp( text, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp ) self.proxy.setFilterRegExp(search) #QtCore.pyqtSlot(int) def on_comboBox_currentIndexChanged(self, index): self.proxy.setFilterKeyColumn(index) if __name__ == "__main__": import sys app = QtGui.QApplication(sys.argv) main = myWindow() main.show() main.resize(400, 600) sys.exit(app.exec_()) When i run the above code i get the below output When i click on Column 2 Header the filter list displayed is as below and its showing correctly (Unique values in that column)... When i select row 0 col 1 in the displayed filter, i get the following filtered list But again when i click on the Column 2 header for filter it shows the same list as my 1st image. All the unique items of column 2 (from model view) and not from proxyfilter. Actually it should show only row 0 col 1 as unique items in Column 2 are only row 0 col 1
It is because you are still using the source model to look up the rows. Data in the original model does not change. Only the filter proxy model reflects the filtered changes. So you just need to modify your lookup in your (really long named) on_view_horizontalHeader_sectionClicked slot: valuesUnique = [ self.proxy.index(row, self.logicalIndex).data().toString() for row in xrange(self.proxy.rowCount()) ] You could also remove a few of the conversion for the unique set: valuesUnique = set( self.proxy.index(row, self.logicalIndex).data().toString() for row in xrange(self.proxy.rowCount()) ) ... for actionNumber, actionName in enumerate(sorted(valuesUnique)): ... And just a small other thing I wanted to point out. You are keeping around a couple of temp objects that don't ever get deleted. In that same slot, you create a new QMenu and QSignalMapper every time but you never clean up the old ones. Over time you are just making more and more. For the QMenu, just make it a local variable and don't parent it to self. That way it will get cleaned up after it disappears. And for the QSignalMapper, you can just use a deleteLater call before creating a new one: # local variable, and no parent menuValues = QtGui.QMenu() # delete the previous one try: self.signalMapper.deleteLater() except: pass self.signalMapper = QtCore.QSignalMapper(self)