在python中怎么实现具备撤销以及重做用户动作的能力.
以下例子展示了这个功能如何在 wxPython 中完成.
专题:wxpython中文教程
例子:
#!/usr/bin/python
#coding=utf-8
#newt.py
from wxPython.lib.sheet import *
from wx.lib.plot import *
import wx
stockUndo = []
stockRedo = []
class UndoText:
def __init__(self, sheet, text1, text2, row, column):
self.RedoText = text2
self.row = row
self.col = column
self.UndoText = text1
self.sheet = sheet
def undo(self):
self.RedoText = self.sheet.GetCellValue(self.row, self.col)
if self.UndoText == None:
self.sheet.SetCellValue('')
else:
self.sheet.SetCellValue(self.row, self.col, self.UndoText)
def redo(self):
if self.RedoText == None:
self.sheet.SetCellValue('')
else:
self.sheet.SetCellValue(self.row, self.col, self.RedoText)
def undo(self):
self.RedoSize = self.sheet.GetColSize(self.pos)
self.sheet.SetColSize(self.pos, self.UndoSize)
self.sheet.ForceRefresh()
def redo(self):
self.UndoSize = 80
self.sheet.SetColSize(self.pos, self.RedoSize)
self.sheet.ForceRefresh()
class UndoRowSize:
def __init__(self, sheet, position, size):
self.sheet = sheet
self.pos = position
self.RedoSize = size
self.UndoSize = 20
def undo(self):
self.RedoSize = self.sheet.GetRowSize(self.pos)
self.sheet.SetRowSize(self.pos, self.UndoSize)
self.sheet.ForceRefresh()
def redo(self):
self.UndoSize = 20
self.sheet.SetRowSize(self.pos, self.RedoSize)
self.sheet.ForceRefresh()
class MySheet(CSheet):
instance = 0
def __init__(self, parent):
CSheet.__init__(self, parent)
self.SetLabelBackgroundColour('#DBD4D4')
self.SetRowLabelAlignment(wx.ALIGN_CENTER,
wx.ALIGN_CENTER)
self.text = ''
def OnCellChange(self, event):
toolbar = self.GetParent().toolbar1
if not toolbar.GetToolEnabled(808):
toolbar.EnableTool(808, True)
r = event.GetRow()
c = event.GetCol()
text = self.GetCellValue(r, c)
#self.text -- 改变之前的文本
#text -- 改变之后的文本
undo = UndoText(self, self.text, text, r, c)
stockUndo.append(undo)
if stockRedo:
#这看起来有些惊讶,不过确实是所有电子表格程序的标准行为
del stockRedo[:]
toolbar.EnableTool(809, False)
def OnColSize(self, event):
toolbar = self.GetParent().toolbar1
if not toolbar.GetToolEnabled(808):
toolbar.EnableTool(808, True)
pos = event.GetRowOrCol()
size = self.GetColSize(pos)
undo = UndoColSize(self, pos, size)
stockUndo.append(undo)
if stockRedo:
del stockRedo[:]
toolbar.EnableTool(809, False)
def OnRowSize(self, event):
toolbar = self.GetParent().toolbar1
if not toolbar.GetToolEnabled(808):
toolbar.EnableTool(808, True)
pos = event.GetRowOrCol()
size = self.GetRowSize(pos)
undo = UndoRowSize(self, pos, size)
stockUndo.append(undo)
if stockRedo:
del stockRedo[:]
toolbar.EnableTool(809, False)
class Newt(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, -4, title, size=(550, 500),
style=wx.DEFAULT_FRAME_STYLE
|wx.NO_FULL_REPAINT_ON_RESIZE)
box = wx.BoxSizer(wx.VERTICAL)
menuBar = wx.MenuBar()
menu1 = wx.Menu()
quit = wx.MenuItem(menu1, 105, "退出(&QtCtrl+Q)", "退出 Newt")
quit.SetBitmap(wx.Image('icons/stock_exit-16.png',
wx.BITMAP_TYPE_PNG).ConvertToBitmap())
menu1.AppendItem(quit)
menuBar.Append(menu1, "文件(&F)")
self.Bind(wx.EVT_MENU, self.OnQuitNewt, id=105)
self.SetMenuBar(menuBar)
#设置工具栏
self.toolbar1 = wx.ToolBar(self, id=-1, style=wx.TB_HORIZONTAL
|wx.NO_BORDER|wx.TB_FLAT|wx.TB_TEXT)
self.toolbar1.AddSimpleTool(808,
wx.Image('icons/stock_undo.png',
wx.BITMAP_TYPE_PNG).
ConvertToBitmap(), '撤销', '')
self.toolbar1.AddSimpleTool(809,
wx.Image('icons/stock_redo.png',
wx.BITMAP_TYPE_PNG).
ConvertToBitmap(), '重做', '')
self.toolbar1.EnableTool(808, False)
self.toolbar1.EnableTool(809, False)
self.toolbar1.AddSeparator()
self.toolbar1.AddSimpleTool(813,
wx.Image('icons/stock_exit.png',
wx.BITMAP_TYPE_PNG).
ConvertToBitmap(), '退出', '')
self.toolbar1.Realize()
self.toolbar1.Bind(wx.EVT_TOOL, self.OnUndo, id=808)
self.toolbar1.Bind(wx.EVT_TOOL, self.OnRedo, id=809)
self.toolbar1.Bind(wx.EVT_TOOL, self.OnQuitNewt, id=813)
box.Add(self.toolbar1, border=5)
box.Add((5, 10), 0)
self.SetSizer(box)
self.sheet1 = MySheet(self)
self.sheet1.SetNumberRows(55)
self.sheet1.SetNumberCols(25)
for i in range(self.sheet1.GetNumberRows()):
self.sheet1.SetRowSize(i, 20)
self.sheet1.SetFocus()
box.Add(self.sheet1, 1, wx.EXPAND)
self.CreateStatusBar()
self.Center()
self.Show(True)
def OnUndo(self, event):
if len(stockUndo) == 0:
return
a = stockUndo.pop()
if len(stockUndo) == 0:
self.toolbar1.EnableTool(808, False)
a.undo()
stockRedo.append(a)
self.toolbar1.EnableTool(809, True)
def OnRedo(self, event):
if len(stockRedo) == 0:
return
a = stockRedo.pop()
if len(stockRedo) == 0:
self.toolbar1.EnableTool(809, False)
a.undo()
stockUndo.append(a)
self.toolbar1.EnableTool(808, True)
def OnQuitNewt(self, event):
self.Close()
app = wx.PySimpleApp()
newt = Newt(None, -1, "Newt")
app.MainLoop()
在这个例子中,想要对单元格文本改变、列和行大小改变上的撤销和重做. 因此必须要将相关事件绑定到我们的方法上.
而这些绑定在示例代码中是看不到的.
可以在 CSheet 类的 sheet.py 文件中找到这些代码:
在上面的 3 个方法中,我们创建了 3 个 Undo 对象. 它们分别是:UndoText、UndoColSize 以及 UndoRowSize,每个对象又有两个方法. undo() 以及 redo(). 两个方法负责在改变完成之前和之后将应用程序带入相应状态. 此后 3 个对象被追加到 stockUndo 清单中. 这样来确保所有必要的改变都给存储起来. 最后,当我们按下撤销、重做按钮,就会调用 OnUndo() 以及 OnRedo()方法。
以下方法调用才是真正完成工作的:
a.undo()
a.redo()
3 个对象随之在 stockUndo 和 stockRedo 清单之间移动.
而在两个清单中不再有对象剩下时,就通过 EnableTool() 方法来令到按钮失效.
如图:
图:newt.py