2015-02-13 15 views
6

Ich arbeite an einem Programm mit einer Reihe von Eingabetabellen, für die ich wxPython wx.Grid (in erster Linie für Windows) verwenden. Ich bemerkte, dass Strg-C und Strg-V zum Kopieren und Einfügen von Rotz einfach funktionieren und ich suchte nach Lösungen, um zu verhindern, dass alle Zahlen in den Tabellen von Hand eingegeben werden mussten. Ich fand einen alten Beitrag von Ruben Charles hier: http://comments.gmane.org/gmane.comp.python.wxpython/26387Arbeite mit ctrl-c und ctrl-v zum Kopieren und Einfügen in ein wx.Grid in wxPython

, die mehr oder weniger zu tun scheint, was ich wollte, und so begann ich mit, dass die Arbeit und machte einige, was ich hoffe, sind Verbesserungen. (Ich habe Funktionen hinzugefügt, die mit ctrl-Z 'rückgängig gemacht' werden, um mit einzelnen Zellen zu arbeiten und einzufügen, wenn die letzte Zeile oder Spalte außerhalb der Grid-Tabelle liegt.)

Gibt es bessere Möglichkeiten, dies zu tun? Haben Sie Verbesserungsvorschläge? Insbesondere: Wie funktioniert das mit Python 3.5? siehe unten

import wx 
import wx.grid 

class MyFrame(wx.Frame): 
    def __init__(self, parent, ID, title, pos=wx.DefaultPosition, size=wx.Size(800, 400), style=wx.DEFAULT_FRAME_STYLE): 
     wx.Frame.__init__(self, parent, ID, title, pos, size, style) 
     agrid = MyGrid(self, -1, wx.WANTS_CHARS) 
     agrid.CreateGrid(7, 7) 
     for count in range(3): 
      for count2 in range(3): 
       agrid.SetCellValue(count, count2, str(count + count2)) 

class MyGrid(wx.grid.Grid): 
    """ A Copy&Paste enabled grid class""" 
    def __init__(self, parent, id, style): 
     wx.grid.Grid.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, style) 
     wx.EVT_KEY_DOWN(self, self.OnKey) 
     self.data4undo = [0, 0, ''] 

    def OnKey(self, event): 
     # If Ctrl+C is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 67: 
      self.copy() 
     # If Ctrl+V is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 86: 
      self.paste('clip') 
     # If Ctrl+Z is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 90: 
      if self.data4undo[2] != '': 
       self.paste('undo') 
     # If del is pressed... 
     if event.GetKeyCode() == 127: 
      # Call delete method 
      self.delete() 
     # Skip other Key events 
     if event.GetKeyCode(): 
      event.Skip() 
      return 

    def copy(self): 
     # Number of rows and cols 
     print self.GetSelectionBlockBottomRight() 
     print self.GetGridCursorRow() 
     print self.GetGridCursorCol() 
     if self.GetSelectionBlockTopLeft() == []: 
      rows = 1 
      cols = 1 
      iscell = True 
     else: 
      rows = self.GetSelectionBlockBottomRight()[0][0] - self.GetSelectionBlockTopLeft()[0][0] + 1 
      cols = self.GetSelectionBlockBottomRight()[0][1] - self.GetSelectionBlockTopLeft()[0][1] + 1 
      iscell = False 
     # data variable contain text that must be set in the clipboard 
     data = '' 
     # For each cell in selected range append the cell value in the data variable 
     # Tabs '\t' for cols and '\r' for rows 
     for r in range(rows): 
      for c in range(cols): 
       if iscell: 
        data += str(self.GetCellValue(self.GetGridCursorRow() + r, self.GetGridCursorCol() + c)) 
       else: 
        data += str(self.GetCellValue(self.GetSelectionBlockTopLeft()[0][0] + r, self.GetSelectionBlockTopLeft()[0][1] + c)) 
       if c < cols - 1: 
        data += '\t' 
      data += '\n' 
     # Create text data object 
     clipboard = wx.TextDataObject() 
     # Set data object value 
     clipboard.SetText(data) 
     # Put the data in the clipboard 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.SetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 

    def paste(self, stage): 
     if stage == 'clip': 
      clipboard = wx.TextDataObject() 
      if wx.TheClipboard.Open(): 
       wx.TheClipboard.GetData(clipboard) 
       wx.TheClipboard.Close() 
      else: 
       wx.MessageBox("Can't open the clipboard", "Error") 
      data = clipboard.GetText() 
      if self.GetSelectionBlockTopLeft() == []: 
       rowstart = self.GetGridCursorRow() 
       colstart = self.GetGridCursorCol() 
      else: 
       rowstart = self.GetSelectionBlockTopLeft()[0][0] 
       colstart = self.GetSelectionBlockTopLeft()[0][1] 
     elif stage == 'undo': 
      data = self.data4undo[2] 
      rowstart = self.data4undo[0] 
      colstart = self.data4undo[1] 
     else: 
      wx.MessageBox("Paste method "+stage+" does not exist", "Error") 
     text4undo = '' 
     # Convert text in a array of lines 
     for y, r in enumerate(data.splitlines()): 
      # Convert c in a array of text separated by tab 
      for x, c in enumerate(r.split('\t')): 
       if y + rowstart < self.NumberRows and x + colstart < self.NumberCols : 
        text4undo += str(self.GetCellValue(rowstart + y, colstart + x)) + '\t' 
        self.SetCellValue(rowstart + y, colstart + x, c) 
      text4undo = text4undo[:-1] + '\n' 
     if stage == 'clip': 
      self.data4undo = [rowstart, colstart, text4undo] 
     else: 
      self.data4undo = [0, 0, ''] 

    def delete(self): 
     # print "Delete method" 
     # Number of rows and cols 
     if self.GetSelectionBlockTopLeft() == []: 
      rows = 1 
      cols = 1 
     else: 
      rows = self.GetSelectionBlockBottomRight()[0][0] - self.GetSelectionBlockTopLeft()[0][0] + 1 
      cols = self.GetSelectionBlockBottomRight()[0][1] - self.GetSelectionBlockTopLeft()[0][1] + 1 
     # Clear cells contents 
     for r in range(rows): 
      for c in range(cols): 
       if self.GetSelectionBlockTopLeft() == []: 
        self.SetCellValue(self.GetGridCursorRow() + r, self.GetGridCursorCol() + c, '') 
       else: 
        self.SetCellValue(self.GetSelectionBlockTopLeft()[0][0] + r, self.GetSelectionBlockTopLeft()[0][1] + c, '') 

class MyApp(wx.App): 
    def OnInit(self): 
     frame = MyFrame(None, -1, "Copy and paste enabled only for a single range") 
     frame.Show(True) 
     self.SetTopWindow(frame) 
     return True 
def main(): 
    app = MyApp() 
    app.MainLoop() 

if __name__ == '__main__': 
    main() 
+1

Perfekt für meine Zwecke. Das einzige, was ich getan habe, war, die Multi-'if-Anweisung in 'OnKey' in ein Dictionary zu ändern, das die anderen Funktionen aufruft und die Paste austeilt und in separaten Fällen unter meiner Paste-Definition rückgängig macht. – Kyrubas

+0

Danke für den Tipp Kyrubas! Ich habe nie daran gedacht, ein Wörterbuch so zu benutzen. – ROB

+0

Danke @ROB! Das ist sehr cool und nützlich. Danke für die Hilfe. – Deathkill14

Antwort

1

ich den Code für die Klasse MyGrid angepasst mit Python 2 und 3 zu arbeiten.

class MyGrid(wx.grid.Grid): 
    """ A Copy&Paste enabled grid class""" 
    def __init__(self, parent, id, style): 
     wx.grid.Grid.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, style) 
     # wx.EVT_KEY_DOWN(self, self.OnKey) 
     self.Bind(wx.EVT_KEY_DOWN, self.OnKey) 
     self.data4undo = [0, 0, ''] 

    def OnKey(self, event): 
     # If Ctrl+C is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 67: 
      self.copy() 
     # If Ctrl+V is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 86: 
      self.paste('clip') 
     # If Ctrl+Z is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 90: 
      if self.data4undo[2] != '': 
       self.paste('undo') 
     # If del is pressed... 
     if event.GetKeyCode() == 127: 
      # Call delete method 
      self.delete() 
     # Skip other Key events 
     if event.GetKeyCode(): 
      event.Skip() 
      return 

    def copy(self): 
     # Number of rows and cols 
     topleft = self.GetSelectionBlockTopLeft() 
     if list(topleft) == []: 
      topleft = [] 
     else: 
      topleft = list(topleft[0]) 
     bottomright = self.GetSelectionBlockBottomRight() 
     if list(bottomright) == []: 
      bottomright = [] 
     else: 
      bottomright = list(bottomright[0]) 
     if list(self.GetSelectionBlockTopLeft()) == []: 
      rows = 1 
      cols = 1 
      iscell = True 
     else: 
      rows = bottomright[0] - topleft[0] + 1 
      cols = bottomright[1] - topleft[1] + 1 
      iscell = False 
     # data variable contain text that must be set in the clipboard 
     data = '' 
     # For each cell in selected range append the cell value in the data variable 
     # Tabs ' ' for cols and '\r' for rows 
     for r in range(rows): 
      for c in range(cols): 
       if iscell: 
        data += str(self.GetCellValue(self.GetGridCursorRow() + r, self.GetGridCursorCol() + c)) 
       else: 
        data += str(self.GetCellValue(topleft[0] + r, topleft[1] + c)) 
       if c < cols - 1: 
        data += ' ' 
      data += '\n' 
     # Create text data object 
     clipboard = wx.TextDataObject() 
     # Set data object value 
     clipboard.SetText(data) 
     # Put the data in the clipboard 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.SetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 

    def paste(self, stage): 
     topleft = list(self.GetSelectionBlockTopLeft()) 
     if stage == 'clip': 
      clipboard = wx.TextDataObject() 
      if wx.TheClipboard.Open(): 
       wx.TheClipboard.GetData(clipboard) 
       wx.TheClipboard.Close() 
      else: 
       wx.MessageBox("Can't open the clipboard", "Error") 
      data = clipboard.GetText() 
      if topleft == []: 
       rowstart = self.GetGridCursorRow() 
       colstart = self.GetGridCursorCol() 
      else: 
       rowstart = topleft[0][0] 
       colstart = topleft[0][1] 
     elif stage == 'undo': 
      data = self.data4undo[2] 
      rowstart = self.data4undo[0] 
      colstart = self.data4undo[1] 
     else: 
      wx.MessageBox("Paste method "+stage+" does not exist", "Error") 
     text4undo = '' 
     # Convert text in a array of lines 
     for y, r in enumerate(data.splitlines()): 
      # Convert c in a array of text separated by tab 
      for x, c in enumerate(r.split(' ')): 
       if y + rowstart < self.NumberRows and x + colstart < self.NumberCols : 
        text4undo += str(self.GetCellValue(rowstart + y, colstart + x)) + ' ' 
        self.SetCellValue(rowstart + y, colstart + x, c) 
      text4undo = text4undo[:-1] + '\n' 
     if stage == 'clip': 
      self.data4undo = [rowstart, colstart, text4undo] 
     else: 
      self.data4undo = [0, 0, ''] 

    def delete(self): 
     # print "Delete method" 
     # Number of rows and cols 
     topleft = list(self.GetSelectionBlockTopLeft()) 
     bottomright = list(self.GetSelectionBlockBottomRight()) 
     if topleft == []: 
      rows = 1 
      cols = 1 
     else: 
      rows = bottomright[0][0] - topleft[0][0] + 1 
      cols = bottomright[0][1] - topleft[0][1] + 1 
     # Clear cells contents 
     for r in range(rows): 
      for c in range(cols): 
       if topleft == []: 
        self.SetCellValue(self.GetGridCursorRow() + r, self.GetGridCursorCol() + c, '') 
       else: 
        self.SetCellValue(topleft[0][0] + r, topleft[0][1] + c, '') 
0

Danke für das tolle Stück Code. Ich brauchte ein benutzerdefiniertes Raster, das Excel wie Einfügen ermöglicht (d. H. Das Kopieren einer einzelnen Zeile oder Spalte in mehrere Zeilen oder Spalten). Ich habe Ihren Code um die zusätzliche Funktionalität erweitert, die ich benötigte. Hier ist meine erweiterte Version Ihres geposteten Codes.

import wx 
import wx.grid 


class MyFrame(wx.Frame): 
    def __init__(self, parent, ID, title, pos=wx.DefaultPosition, 
       size=wx.Size(800, 400), style=wx.DEFAULT_FRAME_STYLE): 
     wx.Frame.__init__(self, parent, ID, title, pos, size, style) 
     agrid = CpGrid(self, -1, wx.WANTS_CHARS) 
     agrid.CreateGrid(7, 7) 
     for count in range(3): 
      for count2 in range(3): 
       agrid.SetCellValue(count, count2, str(count + count2)) 


class CpGrid(wx.grid.Grid): 
    """ A Full Copy and Paste enabled grid class which implements Excel 
    like copy, paste, and delete functionality. 

    Ctrl+c - Copy range of selected cells. 
    Ctrl+v - Paste copy selection at point of currently selected cell. 
      If paste selection is larger than copy selection, 
      copy selection will be replicated to fill paste 
      region if it is a modulo number of copy rows and/or 
      columns, otherwise just the copy selection will be pasted. 
    Ctrl+x - Delete current selection. Deleted selection can be 
      restored with Ctrl+z, or pasted with Ctrl+v. 
      Delete or backspace key will also perform this action. 
    Ctrl+z - Undo the last paste or delete action. 

    """ 

    def __init__(self, parent, id, style): 
     wx.grid.Grid.__init__(self, parent, id, wx.DefaultPosition, 
           wx.DefaultSize, style) 

     # bind key down events 
     wx.EVT_KEY_DOWN(self, self.OnKey) 

     # initialize text string for undo (start row, start col, undo string) 
     self.data4undo = [0, 0, ''] 

     # initialize copy rows and columns 
     # catches case of initial Ctrl+v before a Ctrl+c 
     self.crows = 1 
     self.ccols = 1 

     # initialize clipboard to empty string 
     data = '' 

     # Create text data object 
     clipboard = wx.TextDataObject() 

     # Set data object value 
     clipboard.SetText(data) 

     # Put the data in the clipboard 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.SetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 

    def OnKey(self, event): 
     '''Handles all key events. 
     ''' 

     # If Ctrl+c is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 67: 
      self.copy() 

     # If Ctrl+v is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 86: 
      self.paste('paste') 

     # If Ctrl+Z is pressed... 
     if event.ControlDown() and event.GetKeyCode() == 90: 
      if self.data4undo[2] != '': 
       self.paste('undo') 

     # If del, backspace or Ctrl+x is pressed... 
     if event.GetKeyCode() == 127 or event.GetKeyCode() == 8 \ 
       or (event.ControlDown() and event.GetKeyCode() == 88): 
      # Call delete method 
      self.delete() 

     # Skip other Key events 
     if event.GetKeyCode(): 
      event.Skip() 
      return 

    def copy(self): 
     '''Copies the current range of select cells to clipboard. 
     ''' 
     # Get number of copy rows and cols 
     if self.GetSelectionBlockTopLeft() == []: 
      rowstart = self.GetGridCursorRow() 
      colstart = self.GetGridCursorCol() 
      rowend = rowstart 
      colend = colstart 
     else: 
      rowstart = self.GetSelectionBlockTopLeft()[0][0] 
      colstart = self.GetSelectionBlockTopLeft()[0][1] 
      rowend = self.GetSelectionBlockBottomRight()[0][0] 
      colend = self.GetSelectionBlockBottomRight()[0][1] 

     self.crows = rowend - rowstart + 1 
     self.ccols = colend - colstart + 1 

     # data variable contains text that must be set in the clipboard 
     data = '' 

     # For each cell in selected range append the cell value 
     # in the data variable Tabs '\t' for cols and '\n' for rows 
     for r in range(self.crows): 
      for c in range(self.ccols): 
       data += str(self.GetCellValue(rowstart + r, colstart + c)) 
       if c < self.ccols - 1: 
        data += '\t' 
      data += '\n' 

     # Create text data object 
     clipboard = wx.TextDataObject() 

     # Set data object value 
     clipboard.SetText(data) 

     # Put the data in the clipboard 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.SetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 

    def build_paste_selection(self): 
     '''This method creates the paste selection, builds it 
     into a clipboard string, and puts it on the clipboard. 
     When building the paste selection it fills in replicas 
     of the copy selection if: number of rows and/or columns 
     in the paste selection is larger than the copy selection, 
     and they are multiples of the corresponding copy selection 
     rows and/or columns, otherwise just the copy selection 
     will be used. 
     ''' 

     # Get number of copy rows and cols 
     if self.GetSelectionBlockTopLeft() == []: 
      rowstart = self.GetGridCursorRow() 
      colstart = self.GetGridCursorCol() 
      rowend = rowstart 
      colend = colstart 
     else: 
      rowstart = self.GetSelectionBlockTopLeft()[0][0] 
      colstart = self.GetSelectionBlockTopLeft()[0][1] 
      rowend = self.GetSelectionBlockBottomRight()[0][0] 
      colend = self.GetSelectionBlockBottomRight()[0][1] 

     self.prows = rowend - rowstart + 1 
     self.pcols = colend - colstart + 1 

     # find if paste selection area is a multiple of the copy selection 
     rows_mod = not(bool(self.prows % self.crows)) 
     cols_mod = not(bool(self.pcols % self.ccols)) 

     # initialize to default case (i.e. paste equals copy) 
     row_copies = 1 
     col_copies = 1 

     # one row multiple column paste selection 
     if self.prows == 1 and self.pcols > 1 and cols_mod: 
      col_copies = self.pcols/self.ccols # int division 

     # one col multiple row paste selection 
     if self.prows > 1 and rows_mod and self.pcols == 1: 
      row_copies = self.prows/self.crows # int division 

     # mulitple row and column paste selection 
     if self.prows > 1 and rows_mod and self.pcols > 1 and cols_mod: 
      row_copies = self.prows/self.crows # int division 
      col_copies = self.pcols/self.ccols # int division 

     clipboard = wx.TextDataObject() 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.GetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 

     data = clipboard.GetText() 

     # column expansion (fill out additional columns) 
     out_values = [] 
     for row, text in enumerate(data.splitlines()): 
      string = text 
      for i in range(col_copies - 1): 
       string += '\t' + text 
      out_values.append(string) 

     # row expansion (fill out additional rows) 
     out_values *= row_copies 

     # build output text string for clipboard 
     self.out_data = '\n'.join(out_values) 

    def paste(self, mode): 
     '''Handles paste and undo operations. 
     ''' 

     # perform paste or undo action 
     if mode == 'paste': 
      # create the paste string from the copy string 
      self.build_paste_selection() 

      if self.GetSelectionBlockTopLeft() == []: 
       rowstart = self.GetGridCursorRow() 
       colstart = self.GetGridCursorCol() 
      else: 
       rowstart = self.GetSelectionBlockTopLeft()[0][0] 
       colstart = self.GetSelectionBlockTopLeft()[0][1] 
     elif mode == 'undo': 
      self.out_data = self.data4undo[2] 
      rowstart = self.data4undo[0] 
      colstart = self.data4undo[1] 
     else: 
      wx.MessageBox("Paste method " + mode + " does not exist", "Error") 

     # paste current paste selection and build a clipboard string for undo 
     text4undo = '' # initialize 
     for y, r in enumerate(self.out_data.splitlines()): 
      # Convert c in a array of text separated by tab 
      for x, c in enumerate(r.split('\t')): 
       if y + rowstart < self.NumberRows and \ 
         x + colstart < self.NumberCols: 
        text4undo += str(self.GetCellValue(rowstart + y, 
                 colstart + x)) + '\t' 
        self.SetCellValue(rowstart + y, colstart + x, c) 

      text4undo = text4undo[:-1] + '\n' 

     # save current paste selection for undo 
     if mode == 'paste': 
      self.data4undo = [rowstart, colstart, text4undo] 
     else: 
      self.data4undo = [0, 0, ''] 

    def delete(self): 
     '''This method deletes text from selected cells, places a 
     copy of the deleted cells on the clipboard for pasting 
     (Ctrl+v), and places a copy in the self.data4undo variable 
     for undoing (Ctrl+z) 
     ''' 

     # Get number of delete rows and cols 
     if self.GetSelectionBlockTopLeft() == []: 
      rowstart = self.GetGridCursorRow() 
      colstart = self.GetGridCursorCol() 
      rowend = rowstart 
      colend = colstart 
     else: 
      rowstart = self.GetSelectionBlockTopLeft()[0][0] 
      colstart = self.GetSelectionBlockTopLeft()[0][1] 
      rowend = self.GetSelectionBlockBottomRight()[0][0] 
      colend = self.GetSelectionBlockBottomRight()[0][1] 

     rows = rowend - rowstart + 1 
     cols = colend - colstart + 1 

     # Save deleted text and clear cells contents 
     text4undo = '' 
     for r in range(rows): 
      for c in range(cols): 
       text4undo += \ 
        str(self.GetCellValue(rowstart + r, colstart + c)) + '\t' 
       self.SetCellValue(rowstart + r, colstart + c, '') 

      text4undo = text4undo[:-1] + '\n' 

     # Save a copy of deleted text for undo 
     self.data4undo = [rowstart, colstart, text4undo] 

     # Save a copy of deleted text to clipboard for Ctrl+v 
     clipboard = wx.TextDataObject() 
     clipboard.SetText(text4undo) 
     if wx.TheClipboard.Open(): 
      wx.TheClipboard.SetData(clipboard) 
      wx.TheClipboard.Close() 
     else: 
      wx.MessageBox("Can't open the clipboard", "Error") 


class MyApp(wx.App): 
    def OnInit(self): 
     frame = MyFrame(None, -1, 
         "A copy and paste grid") 
     frame.Show(True) 
     self.SetTopWindow(frame) 
     return True 


def main(): 
    app = MyApp() 
    app.MainLoop() 


if __name__ == '__main__': 
    main()