Hello,
while I was able to code a custom renderer with richtext and wordwrapping capabilities, I found two issues and I like to get some input. For the renderer I append a reduced example. Tested with w2k, py-2.5, pywx-2.8.7.1 and XP,py-2.5.1,pywx-2.8.8.1
1) Taking the charakter style (start,end,fontstring, backgroundcolour, textcolour) from a string, the NativeFontInfoString sets the font size not correctly.
see Line 61 - 64 in the example :
for style in styles:
fn = wx.FontFromNativeInfoString(style['fontstring'])
dc.SetFont(fn)
should work, but results in to small FontSizes. As a workaround I apended:
for style in styles:
fn = wx.FontFromNativeInfoString(style['fontstring'])
fn.SetPointSize(fn.GetPointSize()) # without setting the PointSize again, the font size is false
dc.SetFont(fn)
Is this a bug ?
2) The function GetBestSize of the renderer seems to be called always before the Draw method. As the calculation of the
best cell size is somewhat complex, if there are different font sizes and styles, I call the Draw method inside the GetBestSize method. Are there any reasons to avoid this ? Could you recommend a better way to calculate the BestSize ?
3) For the Editor I need to get the NativeFontInfoString from a TextAttr Object. There is a known bug that the font size is
always 15 x to large see:
http://trac.wxwidgets.org/ticket/2120
both wxFont::GetNativeFontInfoDesc and wxFont::GetNativeFontInfoUserDesc are affected. The bug is known for four years now. Is there any chance that that will corrected in the next few releases ?
Thanks in advance
Jürgen
Example: you can see the wordwrapping by changing the col size
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
import wx
import wx.grid
class MyRichTextWordwrapRenderer(wx.grid.PyGridCellRenderer):
def __init__(self):
wx.grid.PyGridCellRenderer.__init__(self)
# example charakter styles
# start,end, nativefontstring,bgcolour,textcolour
# '#' is used as delimiter between the styles
self.styles ="0,1,0;10;0;0;0;400;0;0;0;0;143;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
1,44,0;10;0;0;0;400;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
44,47,0;10;0;0;0;400;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;0;255,255;0;0;255,#\
47,63,0;10;0;0;0;400;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
63,77,0;14;0;0;0;700;1;1;0;0;152;0;0;16;Times New Roman,255;255;255;255,0;0;255;255,#\
77,90,0;10;0;0;0;400;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
90,94,0;10;0;0;0;700;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
94,107,0;10;0;0;0;400;0;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255,#\
107,113,0;10;0;0;0;400;1;0;0;0;152;0;0;0;MS Shell Dlg 2,255;255;255;255,0;0;0;255"
self.bestsize = (0,0)
def Draw(self, grid, attr, dc, rect, row, col, isSelected):
#prepare styledata
styles = self.styles.split('#')
styleattrs = ["start","end","fontstring","bgcol","txcol"]
for num,item in enumerate(styles):
style = dict(zip(styleattrs, item[:-1].split(',')))
styles[num] = style
# prepare DC
dc.SetBackgroundMode(wx.SOLID)
dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
dc.SetPen(wx.TRANSPARENT_PEN)
dc.DrawRectangleRect(rect)
# calculate maxlineheight, maxlinebase
maxfontsize =max( [x['fontstring'].split(';')[1] for x in styles])
maxfonts = [x['fontstring'] for x in styles if x['fontstring'].split(';')[1] == maxfontsize]
maxlineheight = 0
maxlinebase = 0
for f in maxfonts:
fn = wx.FontFromNativeInfoString(f)
fn.SetPointSize(fn.GetPointSize()) # without setting the PointSize again, the font size is false
dc.SetFont(fn)
h = dc.GetCharHeight()
if maxlineheight < h:
maxlineheight = h
maxlinebase = maxlineheight - dc.GetFullTextExtent('T')[2]
# prepare for wordwrapping
text = grid.GetCellValue(row, col)
linewidth = rect.x + 1
maxlinewidth = rect.x + rect.width
y = rect.y + 1
# write Text with appropriate style
for style in styles:
fn = wx.FontFromNativeInfoString(style['fontstring'])
fn.SetPointSize(fn.GetPointSize()) # without setting the PointSize again, the font size is false
dc.SetFont(fn)
h = dc.GetCharHeight()
linebase = h - dc.GetFullTextExtent('T')[2]
# calc baseline
if h < maxlineheight:
vdiff = maxlinebase - linebase
else:
vdiff = 0
# set colours
r,g,b,a = style['bgcol'].split(';')
bgcol = wx.Colour(int(r),int(g),int(b),int(a))
dc.SetTextBackground(bgcol)
r,g,b,a = style['txcol'].split(';')
tcol = wx.Colour(int(r),int(g),int(b),int(a)) dc.SetTextForeground(tcol)
# get partial Text for the current style
ch = text[int(style['start']):int(style['end'])]
# calc wordwrapping
if linewidth + dc.GetTextExtent(ch)[0] <= maxlinewidth:
dc.DrawText(ch, linewidth, y+vdiff )
linewidth += dc.GetTextExtent(ch)[0]
else:
pte = dc.GetPartialTextExtents(ch)
pos = 0
while pos < len(pte):
if linewidth + pte[pos] > maxlinewidth:
break
pos += 1
# current line
dc.DrawText(ch[:pos], linewidth, y+vdiff )
#next line
y += maxlineheight + 2
linewidth = rect.x + 1
dc.DrawText(ch[pos:], linewidth, y+vdiff )
linewidth += dc.GetTextExtent(ch[pos:])[0]
# set bestsize for the cell
self.bestsize = (maxlinewidth, y + maxlineheight)
def GetBestSize(self, grid, attr, dc, row, col):
# as GetBestSize seems to be called always before Draw
# it's needed to calculate self.bestsize before using it
rect = wx.Rect( 0, 0, grid.GetColSize(col), grid.GetRowSize(row) )
self.Draw( grid, attr, dc, rect,row, col,False)
return self.bestsize
def Clone(self):
return MyCustomRenderer()
class SimpleGrid(wx.grid.Grid):
def __init__(self, parent):
wx.grid.Grid.__init__(self, parent, -1)
self.CreateGrid(2, 3)
self.colLabelWidths = [ 60, 60, 200 ]
for num,width in enumerate(self.colLabelWidths):
self.SetColSize(num, width)
self.SetColMinimalWidth(num, width)
self.SetCellValue(0,2,'''\
If supported by the native control, this is red, and this is a different font.And this is Bold.And this is Italic''')
self.SetCellRenderer(0, 2, MyRichTextWordwrapRenderer())
self.Bind(wx.grid.EVT_GRID_COL_SIZE,self.OnColSize)
self.AutoSize()
def OnColSize(self, evt):
self.AutoSizeRows()
···
#---------------------------------------------------------------------------
class TestFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Simple Grid", size=(640,480))
self.grid = SimpleGrid(self)
#---------------------------------------------------------------------------
if __name__ == '__main__':
app = wx.App(0)
wx.InitAllImageHandlers()
frame = TestFrame(None, )
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
#---------------------------------------------------------------------------