[wxPython-users] Re: wx.Grid.SetDefaultRenderer doesn't seem to be working

Grzegorz Adam Hankiewicz ghankiewicz at rastertech.es
Sat Jan 27 22:53:33 PST 2007


Christian Kristukat wrote:
>> So far I've copied the grid examples from the demo where the cell =

>> renderer is set for individual cells. Since I actually have one
>> type of cell, I loop over all of them setting it. It is however
>> annoying when I try to add new rows, because I have to go again
>> setting their cell renderer.
> =

> If you are using a custom TableBase you can override its GetAttr
> method and and let it return a GridCellAttr with a renderer of your
> choice, e.g.:
> =

> def GetAttr(self, *args): attr =3D wx.grid.GridCellAttr() =

> attr.SetReadOnly() attr.SetRenderer(FloatRenderer()) return attr

Yes, this works.

> I have not tried it but according to what the the docs say I would
> expect it to work. Can you send your code?

Here it is. If you uncomment the lines which set the renderer explicitly
it will work as expected. I'm using 2.8.0.1 under MSW. I wonder if the =

memory leak fixed in 2.8.1.0 (patch 1629949) makes SetDefaultRenderer() =

work.

-- =

Grzegorz Adam Hankiewicz, Jefe de producto de TeraVial
Rastertech Espa=F1a S.A.    Tel: +34 918 467 390, ext 18.
http://www.rastertech.es/    ghankiewicz at rastertech.es
-------------- next part --------------
#!/usr/bin/env python
# vim: tabstop=3D4 shiftwidth=3D4

import random
import sys
import wx
import wx.grid

NUM_DATA =3D 200
ROW_SIZE =3D 30
MAGIC_LIST_HEIGHT =3D 5


class Scroll_binder():
	"""Inherit to be able to bind vscrolling to another widget."""
	def __init__(self):
		"""f() -> Scroll_binder

		Initialises the internal data required for vertical scrolling.
		"""
		self._locked =3D False
		self._bound_widget =3D None
		self._is_list_control =3D hasattr(self, "ScrollList")

		self.Bind(wx.EVT_SCROLLWIN, self._did_scroll)
		self.Bind(wx.EVT_MOUSEWHEEL, self._mousewheel)
		self.Bind(wx.EVT_SCROLLWIN_LINEUP, self._lineup)
		self.Bind(wx.EVT_SCROLLWIN_LINEDOWN, self._linedown)
		self.Bind(wx.EVT_SCROLLWIN_PAGEUP, self._pageup)
		self.Bind(wx.EVT_SCROLLWIN_PAGEDOWN, self._pagedown)
		self.Bind(wx.EVT_SCROLLWIN_TOP, self._top)
		self.Bind(wx.EVT_SCROLLWIN_BOTTOM, self._bottom)
		self.Bind(wx.EVT_KEY_DOWN, self._key_down)


	def bind_scroll(self, to):
		self._bound_widget =3D to


	def _key_down(self, event):
		pass


	def _mousewheel(self, event):
		"""Mouse wheel scrolled. Up or down, give or take."""
		if event.m_wheelRotation > 0:
			do_scroll =3D self._lineup
		else:
			do_scroll =3D self._linedown

		for r in range(event.m_linesPerAction):
			do_scroll()


	def _pageup(self, event):
		"""Clicked on a scrollbar space, performing a page up."""
		if event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		pos =3D self.GetScrollPos(wx.VERTICAL)
		if self._is_list_control:
			amount =3D self.GetCountPerPage()
		else:
			amount =3D self.GetScrollPageSize(wx.VERTICAL)

		print "PageUp %d from %d" % (amount, pos)
		self._bound_widget.scroll_to(max(0, pos - amount))
		self.scroll_to(max(0, pos - amount))


	def _pagedown(self, event):
		"""Clicked on a scrollbar space, performing a page down."""
		if event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		pos =3D self.GetScrollPos(wx.VERTICAL)
		if self._is_list_control:
			amount =3D self.GetCountPerPage()
		else:
			amount =3D self.GetScrollPageSize(wx.VERTICAL)

		print "PageDown %d from %d" % (amount, pos)
		self._bound_widget.scroll_to(pos + amount)
		self.scroll_to(pos + amount)


	def _top(self, event):
		"""Event handler for going to the top."""
		if event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		print "Top"
		self._bound_widget.scroll_to(0)
		self.scroll_to(0)


	def _bottom(self, event):
		"""Event handler for going to the bottom."""
		if event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		print "Bottom"
		pos =3D 10000000
		self._bound_widget.scroll_to(pos)
		self.scroll_to(pos)


	def _lineup(self, event =3D None):
		"""Event handler for pressing the up arrow."""
		if event and event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		pos =3D self.GetScrollPos(wx.VERTICAL)
		print "LineUp from", pos
		self._bound_widget.scroll_to(pos - 1)
		self.scroll_to(pos - 1)


	def _linedown(self, event =3D None):
		"""Event handler for pressing the down arrow."""
		if event and event.GetOrientation() !=3D wx.VERTICAL:
			event.Skip()
			return

		pos =3D self.GetScrollPos(wx.VERTICAL)
		print "LineDown from", pos
		self._bound_widget.scroll_to(pos + 1)
		self.scroll_to(pos + 1)


	def _did_scroll(self, event):
		"""Event handler for manual scrolling."""
		try:
			if event.GetOrientation() !=3D wx.VERTICAL or self._locked:
				return

			print "list pos was %d, will be %d" % (
				self.GetScrollPos(wx.VERTICAL), event.GetPosition())
			self._bound_widget.scroll_to(event.GetPosition())
		finally:
			event.Skip()


	def scroll_to(self, position):
		"""f(int) -> None

		Scrolls to a specific vertical position.
		"""
		self._locked =3D True

		if self._is_list_control:
			dif =3D position - self.GetScrollPos(wx.VERTICAL)
			self.ScrollList(-1, dif * ROW_SIZE)
		else:
			# Presume we are a grid.
			self.Scroll(-1, position)

		self._locked =3D False


class StupidRenderer(wx.grid.PyGridCellRenderer):
	def __init__(self):
		wx.grid.PyGridCellRenderer.__init__(self)

	def Draw(self, grid, attr, dc, rect, row, col, isSelected):
		if isSelected:
			dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID))
		else:
			dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
		dc.SetPen(wx.TRANSPARENT_PEN)
		dc.DrawRectangleRect(rect)

		dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID))
		dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID))
		x =3D rect.x + 2
		y =3D rect.y + 2
		dc.DrawRectangleRect((x, y, 5, 5))

	def Clone(self):
		print "Are we being called?"
		return MyCustomRenderer()


class CustomTableGrid(wx.grid.Grid, Scroll_binder):
	def __init__(self, parent):
		wx.grid.Grid.__init__(self, parent)
		Scroll_binder.__init__(self)
		# This should be setting the renderer... but it doesn't do anything.
		self.SetDefaultRenderer(StupidRenderer())

		self.table =3D CustomDataTable()

		# 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(self.table, True)

		#self.SetRowLabelSize(0)
		self.SetMargins(0, 0)
		self.AutoSizeColumns(True)
		# Disallow rows stretching vertically and set a fixed height.
		self.DisableDragRowSize()
		self.SetRowMinimalAcceptableHeight(ROW_SIZE)
		self.SetDefaultRowSize(ROW_SIZE, True)
		self.SetScrollRate(self.GetScrollLineX(), ROW_SIZE)
		# Don't let the user change the values.
		self.EnableEditing(False)

		# Uncomment these lines to have the renderer working.
		# Putting here the call to SetDefaultRenderer doesn't help.
		#for row, row_data in zip(range(len(self.table.data)), self.table.data):
			#for col, dummy in zip(range(len(row_data)), row_data):
				#print "Setting for row %d, col %d" % (row, col)
				#self.SetCellRenderer(row, col, StupidRenderer())


class CustomDataTable(wx.grid.PyGridTableBase):
	def __init__(self):
		wx.grid.PyGridTableBase.__init__(self)

		self.colLabels =3D ['January', 'February', "March", "April",
			"May", "June", "July", "August", "September", "October",
			"November", "December"]

		self.dataTypes =3D [wx.grid.GRID_VALUE_FLOAT] * 12

		self.data =3D []
		for row in range(NUM_DATA):
			self.data.append([0] * 12)
			for f in range(12):
				self.data[row][f] =3D (random.random(), random.random())

	# This works if you want to have a default renderer.
	#def GetAttr(self, *args):
		#attr =3D wx.grid.GridCellAttr()
		#attr.SetReadOnly()
		#attr.SetRenderer(StupidRenderer())
		#return attr


	# Required methods for the wxPyGridTableBase interface
	def GetNumberRows(self):
		return len(self.data)

	def GetNumberCols(self):
		return len(self.data[0])

	def IsEmptyCell(self, row, col):
		try:
			return not self.data[row][col]
		except IndexError:
			return True

	# Get/Set values in the table.  The Python version of these
	# methods can handle any data-type, (as long as the Editor and
	# Renderer understands the type too,) not just strings as in the
	# C++ version.
	def GetValue(self, row, col):
		try:
			return self.data[row][col]
		except IndexError:
			return ''

	def SetValue(self, row, col, value):
		try:
			self.data[row][col] =3D value
		except IndexError:
			# add a new row
			self.data.append([''] * self.GetNumberCols())
			self.SetValue(row, col, value)

			# tell the grid we've added a row
			msg =3D wx.grid.GridTableMessage(self,            # The table
				wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED, # what we did to it
				1                                       # how many
				)

			self.GetView().ProcessTableMessage(msg)


	#--------------------------------------------------
	# Some optional methods

	# Called when the grid needs to display labels
	def GetColLabelValue(self, col):
		return self.colLabels[col]

	# Called to determine the kind of editor/renderer to use by
	# default, doesn't necessarily have to be the same type used
	# natively by the editor/renderer if they know how to convert.
	def GetTypeName(self, row, col):
		return self.dataTypes[col]

	# Called to determine how the data can be fetched and stored by the
	# editor and renderer.  This allows you to enforce some type-safety
	# in the grid.
	def CanGetValueAs(self, row, col, typeName):
		colType =3D self.dataTypes[col].split(':')[0]
		if typeName =3D=3D colType:
			return True
		else:
			return False

	def CanSetValueAs(self, row, col, typeName):
		return self.CanGetValueAs(row, col, typeName)


class List_control(wx.ListCtrl, Scroll_binder):
	def __init__(self, parent, size, style):
		wx.ListCtrl.__init__(self, parent =3D parent, size =3D size, style =3D st=
yle)
		Scroll_binder.__init__(self)

	def fill(self):
		# Create the images with the desired height.
		self.image_list =3D wx.ImageList(1, ROW_SIZE - 1)
		self.SetImageList(self.image_list, wx.IMAGE_LIST_SMALL)

		self.InsertColumn(0, "Col 1")
		self.InsertColumn(1, "Col 2")
		self.InsertColumn(2, "Col 3")
		for f in range(NUM_DATA):
			self.Append(("id%4d" % (f + 1), "Ref %d" % f, "total"))


class App(wx.App):
	"""Application class."""

	def OnInit(self):
		self.frame =3D wx.Frame(None, title =3D "My title", size =3D (700, 500))
		panel =3D wx.Panel(self.frame)
		self.SetTopWindow(self.frame)

		hbox =3D wx.BoxSizer(wx.HORIZONTAL)
		vbox1 =3D wx.BoxSizer(wx.VERTICAL)
		vbox2 =3D wx.BoxSizer(wx.VERTICAL)

		# The filler panel allows us to set the 'height' of the list.
		self.filler =3D wx.Panel(panel)
		self.list =3D List_control(panel, (300, -1),
			wx.LC_REPORT | wx.ALWAYS_SHOW_SB | wx.VSCROLL)
		self.list.fill()
		vbox1.Add(self.filler, 0, wx.EXPAND | wx.ALL, 0)
		vbox1.Add(self.list, 1, wx.EXPAND | wx.ALL, 5)

		self.grid =3D CustomTableGrid(panel)
		vbox2.Add(self.grid, 1, wx.EXPAND | wx.ALL, 5)
		hbox.Add(vbox1, 0, wx.EXPAND)
		hbox.Add(vbox2, 1, wx.EXPAND)

		# Bind the scrollbars of the widgets.
		self.grid.bind_scroll(self.list)
		self.list.bind_scroll(self.grid)

		self.resize_filler()
		panel.SetSizer(hbox)
		self.frame.Show()
		return True


	def resize_filler(self):
		"""f() -> None

		Resizes the filler panel to adapt the list control height. The position
		of the list control is moved in such a way that its entries correspond
		to the height of the grids entries.
		"""
		# Get the sizes of the list and grid top labels.
		grid_size =3D ROW_SIZE
		# Unfortunately the height of the list labels is not possible to get,
		# hence the use of the magic 4 number.
		list_size =3D self.list.GetCharHeight() + MAGIC_LIST_HEIGHT
		self.filler.SetSize((-1, grid_size - list_size))


def main():
	"""Main entry point."""
	app =3D App(redirect =3D False)
	app.MainLoop()
	print "main() finished running."


if __name__ =3D=3D "__main__":
	main()




More information about the wxpython-users mailing list