Make table with monospaced font Wxpython - python

I am trying to create a table with characters in Wxpython. Something of type:
┌─────────┬──────────┐
│columna1 │ columna2 │
├─────────┼──────────┤
│dato1 │ dato2 │
├─────────┼──────────┤
│dato3 │ dato4 │
└─────────┴──────────┘
I use the code dc.SetFont(wx.Font(12, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
But monospaced seems to not apply to the lines in the table and therefore results in something similar to this:
┌─────────┬──────────┐
│columna1 │ columna2 │
├─────────┼──────────┤
│dato1 │ dato2 │
├─────────┼──────────┤
│dato3 │ dato4 │
└─────────┴──────────┘
I plan to apply the font to text that is going to be sent to a printer. Thank you.
This is an example code that has the error:
import wx
class TextDocPrintout(wx.Printout):
"""
A printout class that is able to print simple text documents.
Does not handle page numbers or titles, and it assumes that no
lines are longer than what will fit within the page width. Those
features are left as an exercise for the reader. ;-)
"""
def __init__(self):#, text, title, margins):
wx.Printout.__init__(self)#, title)
def HasPage(self, page):
return page <= self.numPages
def GetPageInfo(self):
return (1, self.numPages, 1, self.numPages)
def CalculateScale(self, dc):
# Scale the DC such that the printout is roughly the same as
# the screen scaling.
ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()
ppiScreenX, ppiScreenY = self.GetPPIScreen()
logScale = float(ppiPrinterX)/float(ppiScreenX)
# Now adjust if the real page size is reduced (such as when
# drawing on a scaled wx.MemoryDC in the Print Preview.) If
# page width == DC width then nothing changes, otherwise we
# scale down for the DC.
pw, ph = self.GetPageSizePixels()
dw, dh = dc.GetSize()
scale = logScale * float(dw)/float(pw)
# Set the DC's scale.
dc.SetUserScale(scale, scale)
# Find the logical units per millimeter (for calculating the
# margins)
self.numPages = 1
self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
def OnPreparePrinting(self):
# calculate the number of pages
dc = self.GetDC()
self.CalculateScale(dc)
def OnPrintPage(self, page):
dc = self.GetDC()
dc.SetFont(wx.Font(12, wx.TELETYPE, wx.NORMAL, wx.NORMAL))
texto = "┌─────────┬──────────┐\n"
texto += "│columna1 │ columna2 │\n"
texto += "├─────────┼──────────┤\n"
texto += "│dato1 │ dato2 │\n"
texto += "├─────────┼──────────┤\n"
texto += "│dato3 │ dato4 │\n"
texto += "└─────────┴──────────┘"
dc.DrawText(texto, self.logUnitsMM*15, self.logUnitsMM*15)
return True
class PrintFrameworkSample(wx.Frame):
def __init__(self):
wx.Frame.__init__(self)
# initialize the print data and set some default values
self.pdata = wx.PrintData()
self.pdata.SetPaperId(wx.PAPER_A4)
self.pdata.SetOrientation(wx.PORTRAIT)
def OnPrint(self):#, evt):
data = wx.PrintDialogData(self.pdata)
printer = wx.Printer(data)
printout = TextDocPrintout()
useSetupDialog = True
if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:
wx.MessageBox(
"Hubo un problema al imprimir.\n"
"Su impresora está configurada correctamente?",
"Error al Imprimir", wx.OK)
else:
data = printer.GetPrintDialogData()
self.pdata = wx.PrintData(data.GetPrintData()) # force a copy
printout.Destroy()
app=wx.App(False)
PrintFrameworkSample().OnPrint()
app.MainLoop()

Specifying the font facename can force the issue:
dc.SetFont(wx.Font(12, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL, faceName="Monospace"))
However there is a FontEnumerator available, which can be used to select a suitable font from those available.
Here is an example of its use, looking for fixed width fonts.
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
fonts = wx.FontEnumerator()
fonts.EnumerateFacenames(wx.FONTENCODING_SYSTEM,fixedWidthOnly=True)
font_list = fonts.GetFacenames(wx.FONTENCODING_SYSTEM,fixedWidthOnly=True)
list_text = wx.StaticText(self, -1, "Face names:")
self.font = wx.ListBox(self, -1, size=(200, 500), choices=font_list)
self.font_size = wx.SpinCtrl(self, wx.ID_ANY, min=6, max=50, initial=16)
self.sample = wx.StaticText(self, -1, "Sample ")
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(list_text, 0, wx.ALL, 5)
sizer.Add(self.font, 0, wx.ALL, 5)
sizer.Add(self.font_size, 0, wx.ALL, 5)
sizer.Add(self.sample, 0, wx.ALL, 5)
self.SetSizer(sizer)
self.Layout()
self.font.Bind(wx.EVT_LISTBOX, self.OnSelect)
self.font_size.Bind(wx.EVT_SPINCTRL, self.OnSelect)
self.font.SetSelection(0)
self.font_size.SetToolTip('Select font size')
self.OnSelect(None)
def OnSelect(self, evt):
facename = self.font.GetStringSelection()
size = self.font_size.GetValue()
font = wx.Font(size, family=wx.DEFAULT, style=wx.NORMAL, weight=wx.NORMAL, underline=False, faceName=facename)
self.sample.SetLabel(facename)
self.sample.SetFont(font)
self.Refresh()
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None,title="Fixed Width FontEnumerator", size=(800,600))
panel = MyPanel(self)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()

Have you tried doing it using the triple quote? Please, try this and tell me:
texto = '''
┌─────────┬──────────┐
│columna1 │ columna2 │
├─────────┼──────────┤
│dato1 │ dato2 │
├─────────┼──────────┤
│dato3 │ dato4 │
└─────────┴──────────┘
'''
print(texto)
Good luck!

Related

Wxpython producing error

I am trying to run a progrom from the book "Remote Sensing Raster Programming" for calculating NDVI from raster dataset. Program reads two separate raster bands NIR and RED using GDAL and executes following equation NDVI = (NIR-RED)/(NIR+RED).
But it is producing error and GUI is not openning. I am trying to learn wxPython and I will appreciate any help to sort out this problem.
Error -
Traceback (most recent call last):
File "C:\Users\User\Desktop\ndvi.py", line 189, in <module>
frame = MyFrame(None)
File "C:\Users\User\Desktop\ndvi.py", line 48, in __init__
self.make_fb()
File "C:\Users\User\Desktop\ndvi.py", line 112, in make_fb
fileMode=wx.OPEN,
AttributeError: 'module' object has no attribute 'OPEN'
Code for the program
#!/usr/bin/python
# -*- coding: cp1252 -*-
import wx
import wx.lib.filebrowsebutton as filebrowse
import os
# For Image Processing
import numpy
from osgeo import gdalnumeric
from osgeo import gdal
from osgeo import gdal_array
from osgeo.gdalconst import *
# Define satellite bands
redchan = ''
nirchan = ''
# Define output file name
output = ''
# Define Info Message
overview = """Vegetation Index Processing.
Calculates vegetation indices based on biophysical parameters.
NDVI: Normalized Difference Vegetation Index
NDVI = (band 2 - band 1) / (band 2 + band 1)
NDVI = (NIR - Red) / (NIR + Red)
Rouse J., Haas R., Schell J. and Deering D. (1974).
Monitoring vegetation systems in the Great Plains
with ERTS. In Proceedings of the Third Earth
Resources Technology Satellite-1 Symposium, Greenbelt."""
class MyFrame(wx.Frame):
def __init__(self,parent, id=-1, title='Normalized Difference Vegetation Index Processing',
pos=(0,0),
size=(400,400),
style=wx.DEFAULT_FRAME_STYLE):
wx.Frame.__init__(self, parent, id, title, pos, size, style)
self.lognull = wx.LogNull()
# Input Filenames
self.redchan = redchan
self.nirchan = nirchan
self.output = output
# Construct Interface
self.make_text()
self.make_buttons()
self.make_fb()
self.mbox = wx.BoxSizer(wx.VERTICAL)
self.mbox.Add((10,10))
self.mbox.Add(self.text, 1, wx.EXPAND|wx.CENTER, 10)
self.mbox.Add((10,10))
self.mbox.Add((10,10))
self.mbox.Add(self.cc2, 1, wx.EXPAND, 10)
self.mbox.Add(self.cc3, 1, wx.EXPAND, 10)
self.mbox.Add(self.cc6, 1, wx.EXPAND, 10)
self.mbox.Add((10,10))
self.mbox.Add((10,10))
self.mbox.Add(self.bbox, 1, wx.CENTER, 10)
self.mbox.Add((10,10))
self.SetSizer(self.mbox)
self.bindEvents()
# Process Equations, Handling and saving of output
def OnOK(self,event):
print "red: ", self.redchan, " nir:",self.nirchan, " out:", self.output
if(self.redchan==''):
self.OnFileInError()
elif(self.nirchan==''):
self.OnFileInError()
else:
self.redband = gdal_array.LoadFile(self.redchan)
self.nirband = gdal_array.LoadFile(self.nirchan)
# NDVI
self.result=self.ndvi(self.redband, self.nirband)
# prepare/create output file
tmp = gdal.Open(str(self.redchan))
geoT = tmp.GetGeoTransform()
proJ = tmp.GetProjection()
tmp = None
out = gdal_array.OpenArray(self.result )
out.SetGeoTransform( geoT )
out.SetProjection( proJ )
driver = gdal.GetDriverByName( 'GTiff' )
driver.CreateCopy( self.output, out )
self.Destroy()
def ndvi( self, redchan, nirchan ):
"""
Normalized Difference Vegetation Index
ndvi( redchan, nirchan )
"""
result = 1.0*( nirchan - redchan )
result /= 1.0*( nirchan + redchan )
return result
def OnFileInError(self):
dlg = wx.MessageDialog(self,
'Minimum files to add:\n\n Input files => Red and NIR \n One Output file',
'Error',wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
# Path+filename seek and set
def make_fb(self):
# get current working directory
self.dirnm = os.getcwd()
self.cc2 = filebrowse.FileBrowseButton(
self, -1, size=(50, -1), labelText='RED:',
startDirectory = self.dirnm,
fileMode=wx.OPEN,
changeCallback = self.fbbCallback2,
)
self.cc3 = filebrowse.FileBrowseButton(
self, -1, size=(50, -1), labelText='NIR:',
startDirectory = self.dirnm,
fileMode=wx.OPEN,
changeCallback = self.fbbCallback3
)
self.cc6 = filebrowse.FileBrowseButton(
self, -1, size=(50, -1), labelText='OUT File:',
startDirectory = self.dirnm,
fileMask='*.tif',
fileMode=wx.SAVE,
changeCallback = self.fbbCallback6
)
# Collect path+filenames
def fbbCallback2(self, evt):
self.redchan = str(evt.GetString())
def fbbCallback3(self, evt):
self.nirchan = str(evt.GetString())
def fbbCallback6(self, evt):
self.output = str(evt.GetString())
# Front text
def make_text(self):
self.text = wx.StaticText(self, -1, '''\n\tThis is a full Python +
WxPython application,\n\tprocessing NDVI through the use
of \n\tGDAL Python bindings and numpy''')
# Bottom buttons
def make_buttons(self):
self.bbox = wx.BoxSizer(wx.HORIZONTAL)
# OnOK
bmp0 = wx.ArtProvider.GetBitmap(wx.ART_TICK_MARK, wx.ART_TOOLBAR, (32,32))
self.b0 = wx.BitmapButton(self, 20, bmp0, (20, 20),
(bmp0.GetWidth()+50, bmp0.GetHeight()+10), style=wx.NO_BORDER)
self.b0.SetToolTip("Process")
self.bbox.Add(self.b0,1,wx.CENTER,10)
# OnCancel
bmp1 = wx.ArtProvider.GetBitmap(wx.ART_CROSS_MARK, wx.ART_TOOLBAR, (32,32))
self.b1 = wx.BitmapButton(self, 30, bmp1, (20, 20),
(bmp1.GetWidth()+50, bmp1.GetHeight()+10), style=wx.NO_BORDER)
self.b1.SetToolTip("Abort")
self.bbox.Add(self.b1,1,wx.CENTER,10)
# OnInfo
bmp2 = wx.ArtProvider.GetBitmap(wx.ART_HELP, wx.ART_TOOLBAR, (32,32))
self.b2 = wx.BitmapButton(self, 40, bmp2, (20, 20),
(bmp2.GetWidth()+50, bmp2.GetHeight()+10), style=wx.NO_BORDER)
self.b2.SetToolTip("Help/Info.")
self.bbox.Add(self.b2,1,wx.CENTER,10)
def bindEvents(self):
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
self.Bind(wx.EVT_BUTTON, self.OnOK, self.b0)
self.Bind(wx.EVT_BUTTON, self.OnCancel, self.b1)
self.Bind(wx.EVT_BUTTON, self.OnInfo, self.b2)
def OnCloseWindow(self, event):
self.Destroy()
def OnCancel(self, event):
self.Destroy()
def OnInfo(self,event):
dlg = wx.MessageDialog(self, overview,'Help', wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
class MainApp(wx.App):
def OnInit(self):
frame = MainFrame(None)
frame.Show(True)
self.SetTopWindow(frame)
return True
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
If you are using wxPython 4 (Phoenix), then you want to use wx.FD_OPEN instead of wx.OPEN. I'm not sure why that's not in the Migration Guide or the Classic Vs. Phoenix page though

How do I get the wxpython combo box selection and change value?

# -*- coding: utf-8 -*-
import wx
class Main(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(430,550))
self.mainPanel = wx.Panel(self, size=(0,500))
self.data1 = [1,2,3]
self.data2 = ['google','amazon']
self.listCtrl = wx.ListCtrl(self.mainPanel, size=(0,0), style=wx.LC_REPORT|wx.BORDER_SUNKEN)
self.listCtrl.InsertColumn(0, 'ONE', format=wx.LIST_FORMAT_CENTRE, width=wx.LIST_AUTOSIZE_USEHEADER)
self.listCtrl.InsertColumn(1, 'TWO', format=wx.LIST_FORMAT_CENTRE, width=wx.LIST_AUTOSIZE)
self.listCtrl.InsertColumn(2, 'THREE', format=wx.LIST_FORMAT_CENTRE, width=wx.LIST_AUTOSIZE)
self.ComboBoxs = wx.ComboBox(self.mainPanel, choices=self.data2, style=wx.CB_READONLY)
self.ComboBoxs.Bind(wx.EVT_COMBOBOX, self.ComboSelect, self.ComboBoxs)
self.textLabel = wx.StaticText(self.mainPanel)
self.autoRefreshCount = 0
self.BoxSizer = wx.BoxSizer(wx.VERTICAL)
self.BoxSizer.Add(self.ComboBoxs, 0, wx.ALL, 5)
self.BoxSizer.Add(self.listCtrl, 1, wx.EXPAND | wx.ALL, 5)
self.BoxSizer.Add(self.textLabel, 0, wx.EXPAND | wx.ALL, 5)
self.mainPanel.SetSizer(self.BoxSizer)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.autoRefresh, self.timer)
self.timer.Start(5000)
self.ComboSelect(self)
def ComboSelect(self, event):
self.listCtrl.Append(self.data1)
def autoRefresh(self, evnet):
if self.ComboBoxs.GetStringSelection() in self.data2:
self.ComboSelect(self)
self.textLabel.SetLabel('count : ' + str(self.autoRefreshCount))
self.autoRefreshCount += 1
else:
self.textLabel.SetLabel('count : ' + str(0))
self.autoRefreshCount = 0
if __name__ == '__main__':
app = wx.App()
frame = Main()
frame.Show(True)
app.MainLoop()
I have created an automatic import after the combo box selection value.
If the problem changes the combo box selection, the changed value self.textLabel.SetLabel ('count:' + str (self.autoRefreshCount)) must be initialized.
I've tried a lot but I do not know how to do it.
if self.ComboBoxs.GetStringSelection () in self.data2: There seems to be a problem in the conditional expression.
It is not clear what you are trying to achieve in this code.
Your test if self.ComboBoxs.GetStringSelection() in self.data2: is always going to be True because self.ComboBoxs is read only and therefore cannot change, so whatever the selection is, it will always be in self.data2.
Try the following replacement and see if it gets you closer to what you want.
def ComboSelect(self, event):
# self.listCtrl.Append(self.data1)
self.autoRefreshCount = 0
def autoRefresh(self, evnet):
# if self.ComboBoxs.GetStringSelection() in self.data2:
# self.ComboSelect(self)
self.listCtrl.Append(self.data1)
self.textLabel.SetLabel('count : ' + str(self.autoRefreshCount))
self.autoRefreshCount += 1
# else:
# self.textLabel.SetLabel('count : ' + str(0))
# self.autoRefreshCount = 0
Edit:
based on your comment, I suspect that you want is EVT_TEXT this event fires when the text in the combobox changes.
Bind it like this and see if this was what you were looking for.
self.ComboBoxs.Bind(wx.EVT_TEXT, self.ComboChange, self.ComboBoxs)

Transparent panel doesn't updates its background after being moved/dragged in wxPython

I am working with python v2.7 and wxPython v3.0 on Windows 8 OS.
The code provided below simply creates a transparent panel named as myPanel that contains a button. The transparent panel is created on a mainPanel which contains an image as a background.
The transparent panel can be dragged around in the frame.
Problem: After dragging the transparent panel I observed that the background of the transparent panel is not updated automatically. How to update it automatically? How ever if I minimize the gui window and restore it again, the background of the transparent panel is updated automatically! I don't understand the reason of this affect?
I tried using Refresh(), Update() etc. in MouseUp(self, e) method, but unfortunately nothing helped.
Here are the screenshots of the app. The initial state is shown in the image below when the app starts:
After dragging the transparent panel, the background is not updated as shown in the image below:
After minimizing the app window and then restoring it, you'll notice that the background of the transparent panel is updated automatically as shown in the image below:
Code: The image used in the code can be downloaded from here. globe.jpg
import wx
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()
Thank you for your time!
You can create a custom panel and then draw a portion of the globe on that panel based on where it's located on top of the parent frame. This method "fakes" the transparency. I've included an example below.
import wx
class CustomPanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent,-1,size=(80,80))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
parentw,parenth = self.GetParent().GetSize()
image = wx.Image('globe.jpg', wx.BITMAP_TYPE_ANY)
x,y = self.GetPosition()
mywidth,myheight = self.GetSize()
if x + mywidth >= parentw:
mywidth = parentw - x
if y + myheight >= parenth:
myheight = parenth - y
drawx = 0
drawy = 0
if x < 0:
drawx = abs(x)
x = 0
if y < 0:
drawy = abs(y)
y = 0
r = wx.Rect(x,y,mywidth,myheight)
try:
image = image.GetSubImage(r)
except:
# rectangle is out of parent
print 'rect ',r ,' is out of parent frame'
return
bitmap = image.ConvertToBitmap()
pdc = wx.PaintDC(self)
pdc.DrawBitmap(bitmap, drawx, drawy)
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER | wx.CLIP_CHILDREN)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
#self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
self.myPanel = myPanel = CustomPanel(mainPanel)
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
button2 = wx.Button(myPanel, -1, size=(30,30), pos=(40,40))
button2.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
self.myPanel.Refresh()
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()

Auto wrap and newlines in wxPython grid

I want to implement a grid with the cells that have the following behaviour:
cell text should be wrapped if it doesn't fit to the cell
newlines (\n) in the cell text should be processed as well
i.e. the same behaviour as in table editors like MS Excel, OO Calc, etc. when you enable the 'wrap words' option for cells.
I'm trying to do this as follows:
import wx
import wx.grid
class MyGrid(wx.grid.Grid):
def __init__(self, parent = None, style = wx.WANTS_CHARS):
wx.grid.Grid.__init__(self, parent, -1, style = style)
self.CreateGrid(10, 10)
self.editor = wx.grid.GridCellAutoWrapStringEditor()
self.SetDefaultEditor(self.editor)
self.SetDefaultRenderer(wx.grid.GridCellAutoWrapStringRenderer())
self.SetCellValue(0, 0, "Line1\nLine2\nLine3")
self.SetRowSize(0, 100)
class MyFrame(wx.Frame):
def __init__(self, parent = None, title = "Multiline"):
wx.Frame.__init__(self, parent, -1, title)
self.Bind(wx.EVT_CHAR_HOOK, self.on_frame_char_hook)
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(vbox)
grid = MyGrid(panel)
vbox.Add(grid, 1, wx.EXPAND | wx.ALL, 5)
self.grid = grid
btn_exit = wx.Button(panel, -1, "Exit")
vbox.Add(btn_exit, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 10)
#Proceed CTRL+ENTER as newline in the cell editor
def on_frame_char_hook(self, event):
if event.CmdDown() and event.GetKeyCode() == wx.WXK_RETURN:
if self.grid.editor.IsCreated():
self.grid.editor.StartingKey(event)
else:
event.Skip
else:
event.Skip()
if __name__ == "__main__":
app = wx.PySimpleApp()
f = MyFrame()
f.Center()
f.Show()
app.MainLoop()
But this code doesn't work as expected - newlines processed correctly in the cell editor, but ignored in the cell renderer. If I remove the self.SetDefaultRenderer(wx.grid.GridCellAutoWrapStringRenderer()) then newlines processed correctly both in the editor and renderer, but obviously auto wrapping in the renderer doesn't work.
Does anybody know how to solve this?
Solved this problem by writing a custom renderer:
from wx.lib import wordwrap
import wx.grid
class CutomGridCellAutoWrapStringRenderer(wx.grid.PyGridCellRenderer):
def __init__(self):
wx.grid.PyGridCellRenderer.__init__(self)
def Draw(self, grid, attr, dc, rect, row, col, isSelected):
text = grid.GetCellValue(row, col)
dc.SetFont( attr.GetFont() )
text = wordwrap.wordwrap(text, grid.GetColSize(col), dc, breakLongWords = False)
hAlign, vAlign = attr.GetAlignment()
if isSelected:
bg = grid.GetSelectionBackground()
fg = grid.GetSelectionForeground()
else:
bg = attr.GetBackgroundColour()
fg = attr.GetTextColour()
dc.SetTextBackground(bg)
dc.SetTextForeground(fg)
dc.SetBrush(wx.Brush(bg, wx.SOLID))
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawRectangleRect(rect)
grid.DrawTextRectangle(dc, text, rect, hAlign, vAlign)
def GetBestSize(self, grid, attr, dc, row, col):
text = grid.GetCellValue(row, col)
dc.SetFont(attr.GetFont())
text = wordwrap.wordwrap(text, grid.GetColSize(col), dc, breakLongWords = False)
w, h, lineHeight = dc.GetMultiLineTextExtent(text)
return wx.Size(w, h)
def Clone(self):
return CutomGridCellAutoWrapStringRenderer()
For column headers, I was able to insert a \n (newline).
self.m_grid1.SetColLabelValue(8, "Reference \n Level")

Problems with multiple panels in a single notebook page

I am creating a program to calculate D&D scores. I have all the backend done, and I want to get the GUI done now.
What I am trying to do here is have a static panel for certain buttons (next, previous, ok, cancel, etc.). The panel is not cooperating.
I want to try to get it on the bottom right (where next/previous buttons traditionally are). This panel can go in the notebook sizer or in the sizer sizerMain I have made for everything else in step_1.
Let me know if you have any questions. I am very new to wxPython and I hope you can deal with my code... :)
Code is below:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
class step_1(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
# Create initial sizers and panels
## Main sizer, containing both panels
sizerMain = wx.BoxSizer(wx.VERTICAL)
## For the main control area
panelControl = wx.Panel(self,2)
sizerControl = wx.GridBagSizer(hgap = 4,vgap = 4)
## For buttons
panelBtn = wx.Panel(self,1)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
# Add widgets
## Main content area
lblTitle = wx.StaticText(self,label = "Pick Scores")
sizerControl.Add(lblTitle,pos = (0,0),
flag = wx.ALIGN_CENTER|wx.TOP|wx.LEFT|wx.BOTTOM,
border = 5)
btnRoll = wx.Button(self,label = "Roll!")
sizerControl.Add(btnRoll,pos = (0,1),span = (1,5),
flag = wx.EXPAND|wx.ALL,border = 5)
### Radio boxes
#### Radio button tuple
rboxPick = ["Default","Strength","Dexterity","Constitution",
"Intelligence","Wisdom","Charisma"]
self.lblRoll1 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll1,pos = (1,0),flag = wx.ALIGN_CENTER)
self.rboxRoll1 = wx.RadioBox(self,label = "Roll One",choices = rboxPick)
sizerControl.Add(self.rboxRoll1,pos = (1,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
self.lblRoll2 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll2,pos = (2,0),flag = wx.ALIGN_CENTER)
self.rboxRoll2 = wx.RadioBox(self,label = "Roll Two",choices = rboxPick)
sizerControl.Add(self.rboxRoll2,pos = (2,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
self.lblRoll3 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll3,pos = (3,0),flag = wx.ALIGN_CENTER)
self.rboxRoll3 = wx.RadioBox(self,label = "Roll Three",choices = rboxPick)
sizerControl.Add(self.rboxRoll3,pos = (3,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
self.lblRoll4 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll4,pos = (4,0),flag = wx.ALIGN_CENTER)
self.rboxRoll4 = wx.RadioBox(self,label = "Roll Four",choices = rboxPick)
sizerControl.Add(self.rboxRoll4,pos = (4,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
self.lblRoll5 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll5,pos = (5,0),flag = wx.ALIGN_CENTER)
self.rboxRoll5 = wx.RadioBox(self,label = "Roll Five",choices = rboxPick)
sizerControl.Add(self.rboxRoll5,pos = (5,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
self.lblRoll6 = wx.StaticText(self,label = "0")
sizerControl.Add(self.lblRoll6,pos = (6,0),flag = wx.ALIGN_CENTER)
self.rboxRoll6 = wx.RadioBox(self,label = "Roll Six",choices = rboxPick)
sizerControl.Add(self.rboxRoll6,pos = (6,1),span = (1,5),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 2)
### Instructions
self.tcLogger = wx.TextCtrl(self,style = wx.TE_MULTILINE)
sizerControl.Add(self.tcLogger,pos = (7,0),span = (1,6),
flag = wx.EXPAND|wx.LEFT|wx.RIGHT,border = 5)
self.tcLogger.AppendText("""Instructions
1. Click the "Roll!" button up top.
- Scores will be placed in the empty slots on the left side.
2. Look at the scores and decide where you want to put them.
3. Click the correct label for each score.
- Make sure you only assign one score to one ability.
4. Click "Assign" to finalize the assignment.""")
## Button area
self.btnPrev = wx.Button(self,label = "Previous",size = (90,28))
self.btnAssign = wx.Button(self,label = "Assign",size = (90,28))
self.btnNext = wx.Button(self,label = "Next",size = (90,28))
sizerBtn.Add(self.btnPrev)
sizerBtn.Add(self.btnAssign)
sizerBtn.Add(self.btnNext,flag = wx.RIGHT|wx.BOTTOM,border = 5)
self.btnNext.Disable()
self.btnPrev.Disable()
# Set and fit sizers, panels, etc.
## Growable rows and columns
sizerControl.AddGrowableCol(1)
sizerControl.AddGrowableRow(7)
## Finalize sizers and panels
panelControl.SetSizerAndFit(sizerControl)
panelBtn.SetSizerAndFit(sizerBtn)
### Final sizer to hold everything
sizerMain.Add(panelControl,2,wx.EXPAND|wx.ALIGN_TOP|wx.ALL,border = 5)
sizerMain.Add(panelBtn,1,wx.EXPAND|wx.ALIGN_BOTTOM|wx.RIGHT,border = 5)
self.SetAutoLayout(True)
self.SetSizerAndFit(sizerMain)
self.Layout()
# Bind events (as needed)
class step_2(wx.Panel):
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txtOne, 0, wx.ALL, 5)
sizer.Add(txtTwo, 0, wx.ALL, 5)
self.SetSizer(sizer)
class step_3(wx.Panel):
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txtOne, 0, wx.ALL, 5)
sizer.Add(txtTwo, 0, wx.ALL, 5)
self.SetSizer(sizer)
####
# create a button class here for later, don't worry about it now
####
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
# Build the menu bar
menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuFileQuit = menuFile.Append(wx.ID_EXIT, text="&Quit")
#self.Bind(wx.EVT_MENU, self.OnQuit,menuFileQuit)
menuBar.Append(menuFile, "&File")
self.SetMenuBar(menuBar)
p = wx.Panel(self)
nb = wx.Notebook(p)
# create the page windows as children of the notebook
nbPage1 = step_1(nb)
nbPage2 = step_2(nb)
nbPage3 = step_3(nb)
# add the pages to the notebook with the label to show on the tab
nb.AddPage(nbPage1,"Page 1")
nb.AddPage(nbPage2,"Page 2")
nb.AddPage(nbPage3,"Page 3")
# finally, put the notebook in a sizer for the panel to manage the
# layout
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
self.Center()
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,size = (1000,1000),title = "D&D Charcter Creator")
app.MainLoop()
You've got parenting problems!
For example, you want the widget self.lblRoll1 to be on the panelControl therefore you should make it a child of it.
e.g.
self.lblRoll1 = wx.StaticText(panelControl,label = "0")
This is your problem -it occurs throughout your code.
An indispensable tool for solving these type of issues is the Widget Inspection tool.
Also Id advise you to factor out the code for each panel into its own class (which would subclass wx.Panel). This will make it all much easier to read and maintain.

Categories