Domanda

Voglio avere alcune righe colore selezionato essere rosso al posto del colore standard (blu su Windows) in modo che possa indicare lo stato. Qualcuno sa se questo è possibile in wxPython?

È stato utile?

Soluzione

Per poter fare ciò che si vuole, cioè avere un diverso colore di selezione quando sono selezionati alcuni elementi, è necessario cadere in win32. Fortunatamente, non è troppo difficile farlo in python. Ha, tuttavia rendere la vostra piattaforma di codice dipendente. L'ho provato oggi in un piccolo programma. Se il genere non è "Rock" faccio la selezione arancione. Ecco alcuni screenshot.

Articoli Rock selezionati alt text

I prodotti misti selezionati. Si noti la RnB e Blues sono selezionati con Orange alt text http://img258.imageshack.us/img258/1307/soshot2.jpg

Ecco il codice. Sembra spaventoso in un primo momento, ma se conosci qualche Win32, non è poi così male. Faccio uso del href="http://sourceforge.net/projects/pywin32/" rel="nofollow noreferrer"> pywin32 pacchetto e la std ctypes librerie. Dovessi definire alcune delle costanti SDK in quanto non erano disponibili nel modulo win32con.

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()

Altri suggerimenti

Nella classe che derivi da wx.ListCtrl, un'occhiata a sovrascrivendo

def OnGetItemAttr(self, item):
    return self.normalAttr[item % 2]
#

Dove gli attributi voce vengono inizializzati prima del tempo utilizzando:

    self.normalAttr = []
    self.normalAttr.append(wx.ListItemAttr())
    grayAttr = wx.ListItemAttr()
    grayAttr.SetBackgroundColour(lightGray)
    self.normalAttr.append(grayAttr)

Quindi, in questo caso, sto alternando i colori di sfondo tra il default, e un attributo grigio chiaro.

Questa funzione è chiamata per ogni riga come il suo dipinto, quindi è possibile utilizzarlo per indicare tutti i tipi di stato. Se riga è selezionata dovrebbe essere un caso semplice.

scroll top