Refresh a grid with GridTableBase Class - python
I am trying to dynamically load a wxGrid with a pandas Dataframe depending on what table is selected in the combobox. I can get the grid to load on intialization, but can't figure out how to get refresh the GridTableClass and grid. Right now I am trying to test with a random Dataframe.
class PageOne(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
#wx.StaticText(self, -1, "This is a PageOne object", (20,20))
class PageTwo(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
#wx.StaticText(self, -1, "This is a PageTwo object", (40, 40))
class PageThree(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
#wx.StaticText(self, -1, "This is a PageThree object", (60, 60))
class DataTable(gridlib.GridTableBase):
def __init__(self, data):
gridlib.GridTableBase.__init__(self)
self.data = data
#self.colnames = colnames
#Store the row and col length to see if table has changed in size
self._rows = self.GetNumberRows()
self._cols = self.GetNumberCols()
self.odd=gridlib.GridCellAttr()
self.odd.SetBackgroundColour((217,217,217))
self.even=gridlib.GridCellAttr()
self.even.SetBackgroundColour((255,255,255))
def GetAttr(self, row, col, kind):
attr = [self.even, self.odd][row % 2]
attr.IncRef()
return attr
def GetNumberRows(self):
return len(self.data)
def GetNumberCols(self):
return len(self.data.columns) + 1
def IsEmptyCell(self, row, col):
return False
def GetValue(self, row, col):
#if col == 0:
# return None #self.data.index[row]
return self.data.iloc[row, col-1]
def SetValue(self, row, col, value):
self.data.iloc[row, col - 1] = value
def GetColLabelValue(self, col):
if col == 0:
return None
#pass
#return 'Index' if self.data.index.name is None else self.data.index.name
return self.data.columns[col - 1] #[col-1]
#return None
#---------------------------------------------------------------------------
class DataGrid(gridlib.Grid):
def __init__(self, parent, data): # data
gridlib.Grid.__init__(self, parent, - 1) #,colnames,-1 # data
#data = pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD'))
#data.reset_index(drop=True, inplace=True)
table = DataTable(data)
print ("passed")
# The second parameter means that the grid is to take ownership of the
# table and will destroy it when done. Otherwise you would need to keep
# a reference to it and call it's Destroy method later.
self.SetTable(table, True)
self.Bind(gridlib.EVT_GRID_CELL_RIGHT_CLICK, self.OnCellRightClick)
def OnCellRightClick(self, event):
print ("OnCellRightClick: (%d,%d)\n" % (event.GetRow(), event.GetCol()))
#-------------------------------------------------------------------------------
class MainFrame(wx.Frame):
def __init__(self, parent, data): # (self, parent, data):
wx.Frame.__init__(self, parent, -1, "Varkey Foundation") #, size=(640,480))
#Create a panel
self.p = wx.Panel(self)
self.Maximize(True)
#Create blank dataframe
data = pd.DataFrame() #pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD')
#data = pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD'))
#data.reset_index(drop=True, inplace=True)
self.data = DataTable(data)
self.nb = wx.Notebook(self.p)
self.p.SetBackgroundColour( wx.Colour( 0, 0, 0 ) ) # 38,38,38
self.nb.SetBackgroundColour(wx.Colour(58, 56, 56) )
#self.SetBackgroundColour( wx.Colour( 255, 255, 56 ) )
#create the page windows as children of the notebook
self.page1 = PageOne(self.nb)
self.page2 = PageTwo(self.nb)
self.page3 = PageThree(self.nb)
# add the pages to the notebook with the label to show on the tab
self.nb.AddPage(self.page1, "Data")
self.nb.AddPage(self.page2, "Analyze")
self.nb.AddPage(self.page3, "Change Log")
#Create the grid and continue layout
self.grid = DataGrid(self.page1, data)
#grid.SetReadOnly(5,5, True)
#CreateFonts
self.b_font = wx.Font(14,wx.ROMAN,wx.NORMAL,wx.BOLD, True)
self.lbl_font = wx.Font(14,wx.ROMAN,wx.NORMAL,wx.NORMAL, True)
self.cb_font = wx.Font(11,wx.SCRIPT,wx.ITALIC,wx.NORMAL, True)
self.h_font = wx.Font(18,wx.DECORATIVE,wx.ITALIC,wx.BOLD, True)
#Create Title bmp
ico = wx.Icon('varkey_bmp.bmp', wx.BITMAP_TYPE_ICO) #'varkey_frame.bmp'
self.SetIcon(ico)
#Page 1 sizers and widgets
self.title = wx.StaticText(self.page1,label="TITLE",style = wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE)
self.title.SetForegroundColour((255,255,255))
self.title.SetFont(self.h_font)
self.p1_sizer = wx.BoxSizer(wx.VERTICAL)
self.p1_sizer.Add(self.title,0,wx.EXPAND,5)
self.p1_sizer.Add(self.grid,3,wx.EXPAND | wx.ALL ,25)
#self.p1_sizer.Add(self.btn_new,-0,wx.ALIGN_CENTER,5)
self.page1.SetSizer(self.p1_sizer)
#Page 2 sizers and widgets
self.analyze_grid = gridlib.Grid(self.page2)
self.analyze_grid.CreateGrid(0, 10)
self.p2_sizer = wx.BoxSizer(wx.VERTICAL)
self.p2_sizer.Add(self.analyze_grid,1,wx.EXPAND)
self.page2.SetSizer(self.p2_sizer)
#Page 3 sizers and widgets
self.log_grid = gridlib.Grid(self.page3)
self.log_grid.CreateGrid(0, 9)
self.log_grid.EnableEditing(False)
self.p3_sizer = wx.BoxSizer(wx.VERTICAL)
self.p3_sizer.Add(self.log_grid,1,wx.EXPAND)
self.page3.SetSizer(self.p3_sizer)
#Create widgets for top sizer
#Insert Image
self.staticbitmap = wx.StaticBitmap(self.p)
self.staticbitmap.SetBitmap(wx.Bitmap('varkey_logo2.jpg'))
self
self.lbl_user = wx.StaticText(self.p,label="Username:")
self.lbl_password = wx.StaticText(self.p,label="Password:")
self.lbl_interaction = wx.StaticText(self.p,label="Interaction:")
self.lbl_table = wx.StaticText(self.p,label="Table:")
#SetForground colors
self.lbl_user.SetForegroundColour((255,255,255))
self.lbl_password.SetForegroundColour((255,255,255))
self.lbl_interaction.SetForegroundColour((255,255,255))
self.lbl_table.SetForegroundColour((255,255,255))
#Set Fonts
self.lbl_user.SetFont(self.lbl_font)
self.lbl_password.SetFont(self.lbl_font)
self.lbl_interaction.SetFont(self.lbl_font)
self.lbl_table.SetFont(self.lbl_font)
self.tc_user =wx.TextCtrl(self.p,value='cmccall95',size = (130,25))
self.tc_password =wx.TextCtrl(self.p,value='Achilles95', style=wx.TE_PASSWORD | wx.TE_PROCESS_ENTER,size = (130,25))
#self.tc_password.Bind(wx.EVT_TEXT_ENTER,self.onLogin)
self.tc_user.SetFont(self.cb_font)
self.tc_password.SetFont(self.cb_font)
self.btn_login = wx.Button(self.p,label="Login", size=(105,30))
self.btn_login.SetBackgroundColour(wx.Colour(198, 89, 17))
self.btn_login.SetFont(self.b_font)
self.btn_login.Bind(wx.EVT_BUTTON, self.onLogin) #connect_mysql
self.btn_logout = wx.Button(self.p,label="Logout",size=(105,30))
self.btn_logout.SetBackgroundColour(wx.Colour(192,0,0))
self.btn_logout.SetFont(self.b_font)
#self.btn_logout.Bind(wx.EVT_BUTTON, self.onLogout)
self.combo_interaction = wx.ComboBox(self.p, size = (160,25),style = wx.CB_READONLY | wx.CB_SORT | wx.CB_SORT)
#self.combo_interaction.Bind(wx.EVT_COMBOBOX, self.onComboInteraction)
self.combo_table = wx.ComboBox(self.p, size = (160,25),style = wx.CB_READONLY | wx.CB_SORT | wx.CB_SORT)
#self.combo_table.Bind(wx.EVT_COMBOBOX, self.onHideCommands)
self.combo_interaction.SetFont(self.cb_font)
self.combo_table.SetFont(self.cb_font)
#self.combo_table.Bind(wx.EVT_COMBOBOX ,self.OnComboTable)
self.btn_load = wx.Button(self.p,label="Load Table", size=(105,30))
self.btn_load.SetBackgroundColour(wx.Colour(31, 216, 6))
self.btn_load.SetFont(self.b_font)
#self.btn_load.Bind(wx.EVT_BUTTON, self.onLoadData)
self.btn_load.Bind(wx.EVT_BUTTON, self.test_return)
self.lc_change = wx.ListCtrl(self.p,-1,style = wx.TE_MULTILINE | wx.LC_REPORT | wx.LC_VRULES)
self.lc_change.InsertColumn(0,"User ID")
self.lc_change.InsertColumn(1,"Status")
self.lc_change.InsertColumn(2,"Description")
self.lc_change.InsertColumn(3,"Date/Time")
#Set column widths
self.lc_change.SetColumnWidth(0, 75)
self.lc_change.SetColumnWidth(1, 75)
self.lc_change.SetColumnWidth(2, 450)
self.lc_change.SetColumnWidth(3, 125)
#Create Filler text
self.lbl_filler = wx.StaticText(self.p,label="",size = (125,20))
#Create FlexGridSizers(For top half)
self.left_fgs = wx.FlexGridSizer(3,4,25,15)
self.left_fgs.AddMany([(self.lbl_user,1,wx.ALIGN_LEFT | wx.LEFT,15),(self.tc_user,1,wx.EXPAND),(self.lbl_interaction,1,wx.ALIGN_RIGHT|wx.RIGHT, 10),(self.combo_interaction,1,wx.EXPAND),
(self.lbl_password,1,wx.ALIGN_LEFT| wx.LEFT,15),(self.tc_password,1,wx.EXPAND),(self.lbl_table,1,wx.ALIGN_RIGHT|wx.RIGHT, 10),(self.combo_table),
(self.btn_login,2,wx.EXPAND),(self.btn_logout,1,wx.EXPAND),(self.lbl_filler,1,wx.EXPAND),(self.btn_load,1)])
#Create Top Sizer
self.top_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.top_sizer.Add(self.left_fgs,proportion = 1, flag = wx.ALL|wx.EXPAND,border = 30)
self.top_sizer.Add(self.staticbitmap,2,wx.TOP | wx.RIGHT, border = 40) #30
self.top_sizer.Add(self.lc_change,2,wx.RIGHT|wx.EXPAND ,30)
#create Bottom Sizer
self.bottom_sizer = wx.BoxSizer(wx.VERTICAL)
self.bottom_sizer.Add(self.nb,proportion = 5, flag = wx.LEFT |wx.RIGHT | wx.EXPAND,border = 30)
self.mainsizer = wx.BoxSizer(wx.VERTICAL)
self.mainsizer.Add(self.top_sizer,proportion = 0, flag = wx.ALL|wx.EXPAND,border = 5)
self.mainsizer.Add(self.bottom_sizer,proportion = 1,flag = wx.ALL|wx.EXPAND,border = 5)
#self.mainsizer.Add(self.status_sizer,proportion =0,flag = wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, border = 15)
self.p.SetSizerAndFit(self.mainsizer)
def test_reload(self, event):
data = pd.DataFrame() #pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD')
data = pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD'))
#data.reset_index(drop=True, inplace=True)
#self.data = DataTable(data)
self.table.data = self.data
self.grid.Refresh()
#Some more functions.......
if __name__ == '__main__':
import sys
app = wx.App()
frame = MainFrame(None, sys.stdout) # (None, sys.stdout)
frame.Show(True)
app.MainLoop()
I've tried many different variations of the function and can't understand how this should work. From my understanding, I should the table and then call the grid to refresh.
def test_reload(self, event):
data = pd.DataFrame() #pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD')
data = pd.DataFrame(np.random.randint(0,100,size=(200, 5)),columns=list('EFGHD'))
self.table.data = self.data
self.grid.Refresh()
I managed to find a solution. Hopefully this helps someone else out.
To reload the grid, I first create new data. Then I destroy the grid, create a new grid, and insert it in place of the old grid. You must then call Layout(). The Freeze() and Thaw() methods are to prevent screen flickering.
def test_reload(self, event):
self.Freeze()
#Create new data
data = pd.DataFrame(np.random.randint(0,100,size=(40000, 5)),columns=list('EFGHD'))
#Destroy and create grid with new data assigned
self.grid.Destroy()
self.grid = DataGrid(self.page1, data)
#Insert grid into existing sizer
self.p1_sizer.Insert(1,self.grid,1,wx.RIGHT| wx.LEFT|wx.EXPAND, 20)
self.p1_sizer.Layout()
self.Thaw()
I'm sure there is a better method out there, but this works and is instant.
Related
How do I make my wxPython scrolledPanel scroll to the bottom of the window?
Problem: I want to be able to force the scrollbar to the bottom when I call createNewRow(). I can see that the self.Scroll(0, self.scrollRange) is occurring in OnKeyTyped() and the scrollbar moves to the bottom of the window but then the scrollbar moves to the top of the window again. I tried to stop this by binding wx.EVT_SCROLLWIN to OnScroll which calls event.Skip() but it appears that this has not worked. I am out of ideas on how to proceed as I don't know enough about eventHandlers and scroll events in wxPython. Any help on how to proceed would be much appreciated. Full code below. import os import wx import datetime as dt import wx.lib.scrolledpanel as scrolled class MyFrame(wx.Frame): width = 1000 height = 600 today = dt.date.today() today_date = f"{today:%A - %d %B %Y}" filename = f"Worklog {today_date}" wxTHICK_LINE_BORDER = 3 def __init__(self, parent=None, title=filename, size=(width,height - 1)): wx.Frame.__init__(self, parent=parent, title=title, size=size) self.parent = parent self.title = title self.size = size self.BuildMenuBar() def BuildMenuBar(self): # Menu bar self.menuBar = wx.MenuBar() self.fileMenu = wx.Menu() self.NewOpt = wx.MenuItem(self.fileMenu, wx.ID_NEW, '&New\tCtrl+N') self.OpenOpt = wx.MenuItem(self.fileMenu, wx.ID_OPEN, '&Open\tCtrl+O') self.SaveOpt = wx.MenuItem(self.fileMenu, wx.ID_SAVE, '&Save\tCtrl+S') self.QuitOpt = wx.MenuItem(self.fileMenu, wx.ID_EXIT, '&Quit\tCtrl+Q') self.fileMenu.Append(self.NewOpt) self.fileMenu.Append(self.OpenOpt) self.fileMenu.Append(self.SaveOpt) self.fileMenu.Append(self.QuitOpt) self.Bind(wx.EVT_MENU, self.OnQuit, self.QuitOpt) self.menuBar.Append(self.fileMenu, '&File') self.SetMenuBar(self.menuBar) def OnQuit(self, e): self.Close() class MyPanel(wx.Panel): def __init__(self,parent): wx.Panel.__init__(self, parent=parent) self.parent = parent self.size = parent.size panel_colour = wx.Colour(240, 240, 240, 255) self.SetBackgroundColour(panel_colour) self.Refresh() class MyScrolledPanel(scrolled.ScrolledPanel): def __init__(self, parent): scrolled.ScrolledPanel.__init__(self, parent=parent, style = wx.TAB_TRAVERSAL | wx.TB_BOTTOM) self.parent = parent # self.size = parent.size self.width = parent.size[0] self.height = parent.size[1] scrollpanel_colour = wx.Colour(255, 255, 255, 255) self.SetBackgroundColour(scrollpanel_colour) # Call a refresh to update the UI self.Refresh() self.SetAutoLayout(True) self.SetupScrolling() self.InitUI() self.Bind(wx.EVT_SCROLLWIN, self.OnScroll, self) self.Bind(wx.EVT_SIZE, self.OnSize, self) def OnScroll(self, e): e.Skip() def InitUI(self): vgap = 0 hgap = 0 self.rowList = [] self.n = 0 self.scrollSizer = wx.GridBagSizer(vgap + 10, hgap + 10) self.row = self.CreateNewRow(self.n) self.rowList.append(self.row) print(f"Row List: {self.rowList[-1]}") self.scrollSizer.Add(self.row[0], pos = (self.i, 0), flag = wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, border = 10) self.scrollSizer.Add(self.row[1], pos = (self.i, 1), flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL , border = 10) self.scrollSizer.AddGrowableCol(1) self.SetSizer(self.scrollSizer) self.panelSizer = wx.GridBagSizer(vgap, hgap) self.panelSizer.AddGrowableRow(0) self.panelSizer.AddGrowableCol(0) self.panelSizer.Add(self, pos = (0, 0), flag = wx.EXPAND, border = 0) # Add wx.Window not wx.Sizer self.parent.SetSizer(self.panelSizer) def CreateNewRow(self, number): self.i = number self.txtStr = "%02d" % (self.i+1) + ". " self.staticText = wx.StaticText(self, wx.ID_ANY, self.txtStr) #pos = (x, y) #self.staticText.SetForegroundColour(wx.Colour(0,0,0)) self.control = wx.TextCtrl(self, self.i) self.control.SetMaxLength(256) self.text_history_length = 0 self.control.Bind(wx.EVT_TEXT, self.OnKeyTyped, id = self.i) #self.control = wx.TextCtrl(self, -1, pos = (x + w + 5,y) ) #style = wx.TE_MULTILINE elems = [self.staticText, self.control] return elems def OnSize(self, e): self.width, self.height = e.GetSize() self.SetSize((self.width, self.height)) self.OnSizeChange() self.Refresh() def OnSizeChange(self): # Fit child elements self.scrollSizer.FitInside(self) # Resize layout self.Layout() # Resize scrolling self.SetupScrolling() def OnKeyTyped(self, e): self.text_length = len(e.GetString()) if (self.text_history_length == 1 and self.text_length == 0): print(f"History length: {self.text_history_length}") print(f"Text length: {self.text_length}") self.text_history_length = self.text_length pass elif (self.text_history_length == 0 and self.text_length == 1): print(f"History length: {self.text_history_length}") print(f"Text length: {self.text_length}") self.n += 1 self.row = self.CreateNewRow(self.n) print(f"Action: {self.row}") print(f"Row List: {self.rowList[-1]}") self.rowList.append(self.row) self.scrollSizer.Add(self.row[0], pos = (self.n, 0), flag = wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL, border = 10) self.scrollSizer.Add(self.row[1], pos = (self.n, 1), flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL , border = 10) self.SetupScrolling() self.text_history_length = self.text_length self.rowList[self.n-1][1].Bind(wx.EVT_TEXT, None, id = self.n-1) self.text_history_length = 0 else: print(f"History length: {self.text_history_length}") print(f"Text length: {self.text_length}") self.text_history_length = self.text_length self.rowList[-1][1].SetFocus() self.scrolledPanelChild = self.GetChildren()[-1] # [ scrollPanel ] self.ScrollChildIntoView(self.scrolledPanelChild) self.OnSizeChange() self.scrollRange = self.GetScrollRange(wx.VERTICAL) print(f"ScrollRange: {self.scrollRange}") #self.scrollUnits = self.GetScrollPixelsPerUnit() #print(f"ScrollUnit: {self.scrollUnits}") #self.scrollThumb = self.GetScrollThumb(wx.VERTICAL) #print(f"ScrollThumb: {self.scrollThumb}") self.Scroll(0, self.scrollRange) def main(): app = wx.App(False) app.locale = wx.Locale(wx.Locale.GetSystemLanguage()) frame = MyFrame() panel = MyPanel(frame) scrolledPanel = MyScrolledPanel(panel) frame.Show(True) app.MainLoop() if __name__ == "__main__": main()
In : def OnSizeChange(self): # Fit child elements self.scrollSizer.FitInside(self) # Resize layout self.Layout() # Resize scrolling self.SetupScrolling() the SetupScrolling() event causes the scrollbar to reset to the top of the page. So if you comment this the Scroll(0, self.scrollRange) will scroll the scrollbar to the bottom of the page. Improvement would be to get this call to happen after SetupScrolling so that it happens anyway. Maybe the call to SetupScrolling is not necessary in anycase and Layout() is enough.
Old question but still. This can be solved using: wx.CallAfter(self._scrolled_panel.ScrollChildIntoView, new_text) Or calling any of the other scroll methods from the CallAfter. or SetupScrolling(scrollIntoView=True, scrollToTop=False)
WxPython. How to close the cell editor?
How to implement closing your own cell editor when it loses focus? Since the built-in wx editors work. Now my editor closes only if you select another cell in the grid. And, for example, the cell editor (0, 1) closes if you click on the button, and not just when you click on another cell. My editor and renderer: #!/usr/bin/env python # -*- coding: utf-8 -*- import wx import wx.grid class GridCellColourEditor(wx.grid.GridCellEditor): def __init__(self): super().__init__() def Create(self, parent, id, evtHandler): self._cp = wx.ColourPickerCtrl(parent, id) self.SetControl(self._cp) if evtHandler: self._cp.PushEventHandler(evtHandler) def BeginEdit(self, row, col, grid): self.startValue = grid.GetTable().GetValue(row, col) self._cp.SetColour(self.startValue) self._cp.SetFocus() def EndEdit(self, row, col, grid, oldval): val = self._cp.GetColour().GetAsString(wx.C2S_HTML_SYNTAX) if val != oldval: return val else: return None def ApplyEdit(self, row, col, grid): val = self._cp.GetColour().GetAsString(wx.C2S_HTML_SYNTAX) grid.GetTable().SetValue(row, col, val) def Reset(self): self._cp.SetColour(self.startValue) def Clone(self): return GridCellColourEditor() class GridCellColourRenderer(wx.grid.GridCellRenderer): def __init__(self): super().__init__() def Draw(self, grid, attr, dc, rect, row, col, isSelected): if grid.IsEnabled(): bgColour = grid.GetDefaultCellBackgroundColour() else: bgColour = grid.GetBackgroundColour() dc.SetBrush(wx.Brush(bgColour, wx.SOLID)) dc.SetPen(wx.TRANSPARENT_PEN) dc.DrawRectangle(rect) colour = grid.GetTable().GetValue(row, col) x = rect.x + 3 y = rect.y + 3 width = rect.width - 6 height = rect.height - 6 dc.SetBrush(wx.Brush(colour, wx.SOLID)) dc.SetPen(wx.Pen(wx.BLACK)) dc.DrawRoundedRectangle(x, y, width, height, 3) def GetBestSize(self, grid, attr, dc, row, col): return attr.GetSize() def Clone(self): return GridCellColourRenderer() class Frame(wx.Frame): def __init__(self): super().__init__(None) vbox = wx.BoxSizer(wx.VERTICAL) self.grid = wx.grid.Grid(self, size=(100, 50)) vbox.Add(self.grid, flag=wx.EXPAND) self.grid.CreateGrid(1, 2) self.grid.SetCellEditor(0, 0, GridCellColourEditor()) self.grid.SetCellRenderer(0, 0, GridCellColourRenderer()) self.grid.SetCellEditor(0, 1, wx.grid.GridCellTextEditor()) self.grid.SetCellRenderer(0, 1, wx.grid.GridCellStringRenderer()) btn = wx.Button(self, -1, 'For kill focus') vbox.Add(btn, 0, wx.ALL, 10) self.SetSizer(vbox) app = wx.App() frame = Frame() frame.Show() app.MainLoop() Why, if wx.TextCtrl is used as wx.Control, then the cell editor successfully closes when focus is lost. And if you use wx.ColourPickerCtrl, the editor does not close?
It solved my problem class GridCellColourEditor(wx.grid.GridCellEditor): def Create(self, parent, id, evtHandler): self._parent = parent self._colourDialog = None self._colourButton = wx.Button(parent, id, "") self.SetControl(self._colourButton) newEventHandler = wx.EvtHandler() if evtHandler: self._colourButton.PushEventHandler(newEventHandler) self._colourButton.Bind(wx.EVT_BUTTON, self.OnClick) def OnClick(self, event): self._colourButton.SetFocus() self.ShowColourDialog() def SetSize(self, rect): self._colourButton.SetSize(rect.x, rect.y, rect.width + 2, rect.height + 2, wx.SIZE_ALLOW_MINUS_ONE) def Clone(self): return GridCellColourEditor() def BeginEdit(self, row, col, grid): self._grid = grid self._row = row self._col = col self._parent.Bind(wx.EVT_LEFT_DOWN, self.OnParentLeftDown) self._parent.Bind(wx.EVT_KILL_FOCUS, self.OnParentKillFocus) self.startValue = grid.GetTable().GetValue(row, col) self.endValue = self.startValue self._colourButton.SetBackgroundColour(self.startValue) def EndEdit(self, row, col, grid, oldval): self._parent.Unbind(wx.EVT_LEFT_DOWN) self._parent.Unbind(wx.EVT_KILL_FOCUS) if self.endValue != self.startValue: return self.endValue else: return None def ApplyEdit(self, row, col, grid): val = self.endValue.GetAsString(wx.C2S_HTML_SYNTAX) grid.GetTable().SetValue(row, col, val) def Reset(self): self._colourButton.SetBackgroundColour(self.startValue) def ShowColourDialog(self): colourDialog = wx.ColourDialog(self._parent) self._colourDialog = colourDialog colourDialog.GetColourData().SetColour(self.startValue) if colourDialog.ShowModal() == wx.ID_OK: data = colourDialog.GetColourData() colour = data.GetColour() self._colourButton.SetBackgroundColour(colour) self.endValue = colour self._parent.SetFocus() del self._colourDialog self._colourDialog = None def OnParentLeftDown(self, event: wx.MouseEvent): def CheckCellChange(): row = self._grid.GetGridCursorRow() col = self._grid.GetGridCursorCol() if self._row == row and self._col == col: # клик в области сетки, но ячейка не изменилась self._grid.CloseEditControl() wx.CallAfter(CheckCellChange) event.Skip() def OnParentKillFocus(self, event: wx.FocusEvent): if self._parent.FindFocus() != self._colourButton: self._grid.CloseEditControl() event.Skip()
WxPython BitmapButton not clickable
So, I am trying to make this program for a project I was assigned. The code is still a draft and I really didn't know anything about wxPython when I was assigned this project. Anyway. What this program does, is create an application that manages photo albums. I got it to create/remove folders and be able to change its root directory and move the program files elsewhere. I also got it to generate bitmap buttons for each 'album' and place them in a FlexGridSizer. My problem is that these Bitmap Buttons are unclickable. class RightPanel(wx.Panel): global path def __init__(self, parent): a = wx.GetDisplaySize() width = 3 * a[0] / 4 height = 3 * a[1] / 4 wx.Panel.__init__(self, parent=parent, size=(3*width/4, height), style=wx.EXPAND) self.SetBackgroundColour('dark grey') self.widgetSizer = wx.BoxSizer(wx.VERTICAL) class MasterPanel(wx.Panel): global delete, CurrentDirg, locale delete = False a = wx.GetDisplaySize() width = 3 * a[0] / 4 height = 3 * a[1] / 4 id = {} def __init__(self, parent): wx.Panel.__init__(self, parent) self.MasterPanel = wx.Panel(self, wx.ID_ANY, size=(self.width, self.height), ) self.SetBackgroundColour('light grey') self.sizer = wx.BoxSizer(wx.VERTICAL) splitter1 = wx.SplitterWindow(self) splitter2 = wx.SplitterWindow(splitter1) left_pt = LeftPanelTop(splitter2) left_pb = LeftPanelBottom(splitter2) self.rightP = RightPanel(splitter1) self.boxsizer2 = wx.BoxSizer(wx.VERTICAL) splitter2.SetSashGravity(0.5) splitter2.SplitHorizontally(left_pt, left_pb) splitter1.SplitVertically(splitter2, self.rightP) splitter1.SetSashGravity(0.5) self.gSizer = wx.FlexGridSizer(0, 5, 10, 10) self.dir_search() self.boxsizer2.Add(self.gSizer, 1, wx.EXPAND|wx.ALL) self.rightP.SetSizer(self.boxsizer2) self.boxsizer2.Layout() self.sizer.Add(splitter1, 1, wx.EXPAND) self.SetSizer(self.sizer) def dir_search(self): global path, delete try: if self.id != {} or delete == True: sizer = self.gSizer for i in sizer.GetChildren(): sizer.Hide(0) sizer.Remove(0) self.boxsizer2.Layout() self.gSizer.Layout() self.id = {} with open('albums.dir', mode='r', buffering=1) as alb: names = alb.readlines() for i in range(len(names)): names[i] = names[i].rstrip('\n') paths = [path + '\\' + i for i in names] counter = 0 for i in paths: self.dirimcreate(i, counter) counter += 1 print(self.id) except Exception as E: print(E) sizer = self.gSizer while sizer.GetChildren(): sizer.Hide(0) sizer.Remove(0) self.boxsizer2.Layout() def dirimcreate(self, path, counter): pic = wx.Image('input.ico', wx.BITMAP_TYPE_ANY) pic = pic.Scale(self.width / 10, self.width / 10, wx.IMAGE_QUALITY_HIGH) pic = pic.ConvertToBitmap() self.saasda = wx.BitmapButton(self.rightP, wx.ID_ANY, pic, size=(self.width / 10, self.width / 10), style=wx.NO_BORDER ) self.saasda.Bind(wx.EVT_BUTTON, self.chdir) self.saasda.SetDefault() self.saasda.myname = self.saasda.GetId() self.id[self.saasda.GetId()] = path self.gSizer.Add(self.saasda, 0, wx.ALL, 5) self.boxsizer2.Layout() def chdir(self, event): self.Info(message='You clicked a button') This is what the result looks like. Thank you in advance.
I found the solution! It seems that the code for the MasterPanel class included a size parameter in the init, which created an invisible panel that covered everything else, rendering it unclickable.
WxPython's ScrolledWindow element collapses to minimum size
I am using a Panel within a Frame to display images (the GUI need to switch between multiple panels and hence the hierarchy). As images should be displayed in native size I used ScrolledWindow as the panel parent. The scrolls do appear and work, but it causes the Panel to collapse to minimum size and it needs to be resized using drag&drop every time. Is there a way around this? Below is a reduced version of the code, which shows the problem: import os import wx from wx.lib.pubsub import pub class Edit_Panel(wx.PyScrolledWindow): def __init__(self, parent): super(Edit_Panel, self).__init__(parent) # Display size width, height = wx.DisplaySize() self.photoMaxSize = height - 500 # Loaded image self.loaded_image = None # Icons self.open_icon_id = 500 # Generate panel self.layout() def layout(self): self.main_sizer = wx.BoxSizer(wx.VERTICAL) divider = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL) self.main_sizer.Add(divider, 0, wx.ALL | wx.EXPAND) self.toolbar = self.init_toolbar() self.main_sizer.Add(self.toolbar, 0, wx.ALL) img = wx.EmptyImage(self.photoMaxSize, self.photoMaxSize) self.image_control = wx.StaticBitmap(self, wx.ID_ANY, wx.BitmapFromImage(img)) self.main_sizer.Add(self.image_control, 0, wx.ALL | wx.CENTER, 5) self.image_label = wx.StaticText(self, -1, style = wx.ALIGN_CENTRE) self.main_sizer.Add(self.image_label, 0, wx.ALL | wx.ALIGN_CENTRE, 5) self.SetSizer(self.main_sizer) fontsz = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT).GetPixelSize() self.SetScrollRate(fontsz.x, fontsz.y) self.EnableScrolling(True, True) def init_toolbar(self): toolbar = wx.ToolBar(self) toolbar.SetToolBitmapSize((16, 16)) open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16, 16)) open_tool = toolbar.AddSimpleTool(self.open_icon_id, open_ico, "Open", "Open an Image Directory") handler = self.on_open_reference self.Bind(event = wx.EVT_MENU, handler = handler, source = open_tool) toolbar.Realize() return toolbar def on_open_reference(self, event, wildcard = None): if wildcard is None: wildcard = self.get_wildcard() defaultDir = '~/' dbox = wx.FileDialog(self, "Choose an image to display", defaultDir = defaultDir, wildcard = wildcard, style = wx.OPEN) if dbox.ShowModal() == wx.ID_OK: file_name = dbox.GetPath() # load image self.load_image(image = file_name) dbox.Destroy() def get_wildcard(self): wildcard = 'Image files (*.jpg;*.png;*.bmp)|*.png;*.bmp;*.jpg;*.jpeg' return wildcard def load_image(self, image): self.loaded_image = image # Load image img = wx.Image(image, wx.BITMAP_TYPE_ANY) # Label image name image_name = os.path.basename(image) self.image_label.SetLabel(image_name) # scale the image, preserving the aspect ratio scale_image = True if scale_image: W = img.GetWidth() H = img.GetHeight() if W > H: NewW = self.photoMaxSize NewH = self.photoMaxSize * H / W else: NewH = self.photoMaxSize NewW = self.photoMaxSize * W / H img = img.Scale(NewW, NewH) self.image_control.SetBitmap(wx.BitmapFromImage(img)) # Render self.main_sizer.Layout() self.main_sizer.Fit(self) self.Refresh() pub.sendMessage("resize", msg = "") class Viewer_Frame(wx.Frame): def __init__(self, parent, id, title): super(Viewer_Frame, self).__init__(parent = parent, id = id, title = title) # Edit panel self.edit_panel = Edit_Panel(self) # Default panel self.main_panel = self.edit_panel # Render frame self.render_frame() # Subscription to re-render pub.subscribe(self.resize_frame, ("resize")) def render_frame(self): # Main Sizer self.main_sizer = wx.BoxSizer(wx.VERTICAL) # Add default sizer self.main_sizer.Add(self.main_panel, 1, wx.EXPAND) # Render self.SetSizer(self.main_sizer) self.Show() self.main_sizer.Fit(self) self.Center() def resize_frame(self, msg): self.main_sizer.Fit(self) if __name__ == "__main__": app = wx.App(False) frame = Viewer_Frame(parent = None, id = -1, title = 'Toolkit') app.MainLoop()
You're calling Fit(), so you're explicitly asking the panel to fit its contents, but you don't specify the min/best size of this contents anywhere (AFAICS, there is a lot of code here, so I could be missing something). If you want to use some minimal size for the panel, just set it using SetMinSize().
Modifying minimal width of left tab style of wx Python's agw LabelBook?
I'm trying to use wx Python's AGW LabelBook (using wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04), such that the tabs (list) are left-aligned; here I have some short texts, and I expected the tablist area would have its width shortened accordingly; but instead I get this: At that mouse position, I get a sizer pointer - and I can drag it to the right to increase the width of the tablist area as much as I want; but I cannot drag it any further to the left, to make the width shorter. I also tried to use INB_FIT_LABELTEXT, but it doesn't seem to change anything... Is it possible to somehow instruct LabelBook to set the minimal width of the left tablist area to the approx width of text (say, indicated at the drawn red line)? This is the code I used to generate the screenshot: import wx import wx.lib.agw import wx.lib.agw.labelbook as LB from wx.lib.agw.fmresources import * class MyFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Labelbook test") self.tlbook = LB.LabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON) sizer_1 = wx.BoxSizer(wx.VERTICAL) self.tlbook_panel_1 = wx.Panel(self.tlbook) self.tlbook_panel_2 = wx.Panel(self.tlbook) self.tlbook.AddPage(self.tlbook_panel_1, "Test 1") self.tlbook.AddPage(self.tlbook_panel_2, "Test 2") sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0) self.SetSizer(sizer_1) sizer_1.Fit(self) self.SetSize((450, 250)) self.Layout() app = wx.App(0) frame = MyFrame(None) app.SetTopWindow(frame) frame.Show() app.MainLoop()
Ok, I think I got it: ... and the thing is, that the width of the tab area is hardcoded in the source for LabelBook as 100 pixels, but not all in the same class - so some monkeypatching is required, if one wants to leave the source in tact. Here is the code: import wx import wx.lib.agw import wx.lib.agw.labelbook as LB #~ from wx.lib.agw.labelbook import * # for INB_BOLD_TAB_SELECTION = 16384? nope INB_BOLD_TAB_SELECTION = 16384 from wx.lib.agw.fmresources import * WIDTHLIMITPIX=20 class OLabelContainer(LB.LabelContainer): # overloaded version def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelContainer"): super(OLabelContainer, self).__init__(parent, id, pos, size, style, agwStyle, name) def Resize(self, event): # copy from wx/lib/agw/labelbook.py # Resize our size self._tabAreaSize = self.GetSize() newWidth = self._tabAreaSize.x x = event.GetX() if self.HasAGWFlag(INB_BOTTOM) or self.HasAGWFlag(INB_RIGHT): newWidth -= event.GetX() else: newWidth = x # hack: was 100 here if newWidth < WIDTHLIMITPIX: #100: # Dont allow width to be lower than that newWidth = WIDTHLIMITPIX #100 self.SetSizeHints(newWidth, self._tabAreaSize.y) # Update the tab new area width self._nTabAreaWidth = newWidth self.GetParent().Freeze() self.GetParent().GetSizer().Layout() self.GetParent().Thaw() LB.LabelContainer = OLabelContainer # do monkeypatch old class class MyLabelBook(LB.LabelBook): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelBook"): super(MyLabelBook, self).__init__(parent, id, pos, size, style, agwStyle, name) self._fontSizeMultiple = 1.0 self._fontBold = False print(self._pages) # is OLabelContainer, OK def GetFontBold(self): # copy from wx/lib/agw/labelbook.py return self._fontBold def ResizeTabArea(self): # copy from wx/lib/agw/labelbook.py agwStyle = self.GetAGWWindowStyleFlag() if agwStyle & INB_FIT_LABELTEXT == 0: return if agwStyle & INB_LEFT or agwStyle & INB_RIGHT: dc = wx.MemoryDC() dc.SelectObject(wx.EmptyBitmap(1, 1)) font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetPointSize(font.GetPointSize()*self._fontSizeMultiple) if self.GetFontBold() or agwStyle & INB_BOLD_TAB_SELECTION: font.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(font) maxW = 0 for page in xrange(self.GetPageCount()): caption = self._pages.GetPageText(page) w, h = dc.GetTextExtent(caption) maxW = max(maxW, w) maxW += 24 #TODO this is 6*4 6 is nPadding from drawlabel if not agwStyle & INB_SHOW_ONLY_TEXT: maxW += self._pages._nImgSize * 2 maxW = max(maxW, WIDTHLIMITPIX) # hack: was 100 here self._pages.SetSizeHints(maxW, -1) self._pages._nTabAreaWidth = maxW class MyFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Labelbook test") self.tlbook = MyLabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON) sizer_1 = wx.BoxSizer(wx.VERTICAL) self.tlbook_panel_1 = wx.Panel(self.tlbook) self.tlbook_panel_2 = wx.Panel(self.tlbook) self.tlbook.AddPage(self.tlbook_panel_1, "Test 1") self.tlbook.AddPage(self.tlbook_panel_2, "Test 2") sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0) self.SetSizer(sizer_1) sizer_1.Fit(self) self.SetSize((450, 250)) self.Layout() app = wx.App(0) frame = MyFrame(None) app.SetTopWindow(frame) frame.Show() app.MainLoop()