2010-02-15 11 views
6

Ich habe ein Dilemma zu haben. Ich habe ein EXCEL 2003-Template, mit dem Benutzer Tabellendaten ausfüllen können. Ich habe Validierungen für verschiedene Zellen und jede Zeile unterliegt einer ziemlich komplexen VBA-Validierung bei Change- und Selection_change-Ereignissen. Das Blatt ist geschützt, um Formatierungsaktivitäten zu verbieten, Zeilen und Spalten einzufügen und zu löschen usw.Excel - Taktik für komplexe Validierung

Solange Benutzer die Tabelle Zeile für Zeile ausfüllen, funktioniert alles recht gut. Die Situation wird schlechter, wenn ich dem Benutzer das Kopieren/Einfügen von Daten in dieses Blatt erlauben möchte (was in diesem Fall eine legitime Benutzeranforderung ist), da die Zellenvalidierung das Einfügen von Aktionen verbieten würde.

So habe ich versucht, Benutzern zu ermöglichen, den Schutz zu deaktivieren und ausschneiden/einfügen, ein VBA markiert das Blatt, um anzuzeigen, dass es nicht validierte Einträge enthält. Ich habe eine "Batch-Validierung" erstellt, die alle nicht leeren Zeilen gleichzeitig überprüft. Immer noch Kopieren/Einfügen funktioniert nicht so gut (muss direkt vom Quellblatt zum Ziel springen, kann nicht aus Textdateien einfügen, etc.)

Zellvalidierung ist auch nicht gut von dem Punkt des Einfügens von Zeilen, denn je nachdem wo Wenn Sie die Zeile einfügen, fehlt möglicherweise die Zellenvalidierung vollständig. Und wenn ich Zellvalidierungen in Zeile 65k kopiere, wird das leere Blatt über 2M groß - ein weiterer unerwünschter Nebeneffekt.

Also dachte ich, ein Weg zur Umgehung von Problemen wäre, die Zellvalidierung zu vergessen und nur VBA zu verwenden. Dann würde ich den Benutzerkomfort des Bereitstellens von Dropdown-Listen in einigen Spalten opfern - von denen einige sich auch als Funktion von Einträgen in anderen Spalten ändern.

Hat jemand in der gleichen Situation gewesen und kann mir einige (generische) taktische Ratschläge geben (Codierung VBA ist kein Problem)?

Mit freundlichen Grüßen MikeD

Antwort

1

Hier ist, was ich gefunden habe (alle Excel 2003)

Alle Arbeitsblätter in meiner Arbeitsmappe erfordern eine komplexe Validierung sind in Tabellenform mit ein paar Kopfzeilen mit Blatt Titel und Spaltentitel organisiert. Alle Spalten rechts von der letzten sind ausgeblendet, und alle Zeilen unterhalb einer praktischen Grenze (in meinem Fall 200 Zeilen) sind ebenfalls ausgeblendet. Ich habe folgende Module ein:

  • GlobalDefs ... Aufzählungen
  • CommonFunctions ... verwendete Funktionen durch alle Blätter
  • Sheet_X_Functions ...Funktionen insbesondere auf ein einzelnes Blatt
  • und Ereignis löst in Sheet_X selbst

Die Aufzählungen rein Zweck der Vermeidung dienen, zu codieren; Wenn ich Spalten hinzufügen oder entfernen möchte, bearbeite ich meistens die Enums, während ich im realen Code die symbolischen Namen für jede Spalte verwende. Das hört sich vielleicht etwas übertrieben an, aber ich habe gelernt, es zu lieben, als Benutzer zum dritten Mal kamen und bat mich, die Tabellenlayouts zu ändern.

' module GlobalDefs 
Public Enum T_Sheet_X 
    NofHRows = 3 ' number of header rows 
    NofCols = 36 ' number of columns 
    MaxData = 203 ' last row validated 
    GroupNo = 1  ' symbolic name of 1st column 
    CtyCode = 2  ' ... 
    Country = 3 
    MRegion = 4 
    PRegion = 5 
    City = 6 
    SiteType = 7 
    ' etc 
End Enum 

Zuerst beschreibe ich Code, der Ereignis ausgelöst wird.

Vorschläge in diesem Thread sollten PASTE-Aktivitäten abfangen. Nicht wirklich unterstützt von einem Event-Trigger in Excel-2003, aber letztendlich kein großes Wunder. Das Einfangen/Entnehmen der PASTE erfolgt beim Aktivieren/Deaktivieren von Ereignissen in Sheet_X. Bei Deaktivierung überprüfe ich auch den Schutzstatus. Wenn ungeschützt, bitte ich den Benutzer, einer Batch-Validierung zuzustimmen und erneut zu schützen. Single-Line-Validierung und Batch-Validierung Routinen sind dann Code-Objekte im Modul Sheet_X_Functions weiter unten beschrieben.

' object in Sheet_X 
Private Sub Worksheet_Activate() 
' suspend PASTE 
    Application.CommandBars("Edit").Controls("Paste").OnAction = "TrappedPaste" ' main menu 
    Application.CommandBars("Edit").Controls("Paste Special...").OnAction = "TrappedPaste" ' main menu 
    Application.CommandBars("Cell").Controls("Paste").OnAction = "TrappedPaste" ' context menu 
    Application.CommandBars("Cell").Controls("Paste Special...").OnAction = "TrappedPaste" ' context menu 
    Application.OnKey "^v", "TrappedPaste" ' key shortcut 
End Sub 

' object in Sheet_X 
Private Sub Worksheet_Deactivate() 
' checks protection state, performs batch validation if agreed by user, and restores normal PASTE behaviour 
' writes a red reminder into cell A4 if sheet is left unvalidated/unprotected 
Dim RetVal As Integer 
    If Not Me.ProtectContents Then 
     RetVal = MsgBox("Protection is currently turned off; sheet may contain inconsistent data" & vbCrLf & vbCrLf & _ 
         "Press OK to validate sheet and protect" & vbCrLf & _ 
         "Press CANCEL to continue at your own risk without protection and validation", vbExclamation + vbOKCancel, "Validation") 
     If RetVal = vbOK Then 
      ' silent batch validation 
      Application.ScreenUpdating = False 
      Sheet_X_BatchValidate Me 
      Application.ScreenUpdating = True 
      Me.Cells(1, 4) = "" 
      Me.Cells(1, 4).Interior.ColorIndex = xlColorIndexNone 
      SetProtectionMode Me, True 
     Else 
      Me.Cells(1, 4) = "unvalidated" 
      Me.Cells(1, 4).Interior.ColorIndex = 3 ' red 
     End If 
    ElseIf Me.Cells(1, 4) = "unvalidated" Then 
     ' silent batch validation ... user manually turned back protection 
     SetProtectionMode Me, False 
     Application.ScreenUpdating = False 
     Sheet_X_BatchValidate Me 
     Application.ScreenUpdating = True 
     Me.Cells(1, 4) = "" 
     Me.Cells(1, 4).Interior.ColorIndex = xlColorIndexNone 
     SetProtectionMode Me, True 
    End If 
    ' important !! restore normal PASTE behaviour 
    Application.CommandBars("Edit").Controls("Paste").OnAction = "" 
    Application.CommandBars("Edit").Controls("Paste Special...").OnAction = "" 
    Application.CommandBars("Cell").Controls("Paste").OnAction = "" 
    Application.CommandBars("Cell").Controls("Paste Special...").OnAction = "" 
    Application.OnKey "^v" 
End Sub 

Modul Sheet_X_Functions enthält im Grunde der spezifischen Validierungsunter zu diesem Blatt. Beachten Sie die Verwendung von Enum ist hier - es ist wirklich für mich gelohnt - vor allem in der Sheet_X_ValidateRow Routine - Benutzer hat mich gezwungen, dies eine 100 Mal fühlte sich ändern;)

' module Sheet_X_Functions 
Sub Sheet_X_BatchValidate(MySheet As Worksheet) 
Dim VRow As Range 
    For Each VRow In MySheet.Rows 
     If VRow.Row > T_Sheet_X.NofHRows And VRow.Row <= T_Sheet_X.MaxData Then 
      Sheet_X_ValidateRow VRow, False ' silent validation 
     End If 
    Next 
End Sub 

Sub Sheet_X_ValidateRow(MyLine As Range, Verbose As Boolean) 
' Verbose: TRUE .... display message boxes; FALSE .... keep quiet (for batch validations) 
Dim IsValid As Boolean, Idx As Long, ProfSum As Variant 

    IsValid = True 
    If ContainsData(MyLine, T_Sheet_X.NofCols) Then 
     If MyLine.Cells(1, T_Sheet_X.Country) = "" Or _ 
      MyLine.Cells(1, T_Sheet_X.City) = "" Or _ 
      MyLine.Cells(1, T_Sheet_X.SiteType) = "" Then 
      If Verbose Then MsgBox "Site information incomplete", vbCritical + vbOKOnly, "Row validation" 
      IsValid = False 
     ' ElseIf otherstuff 
     End If 

     ' color code the validation result in 1st column 
     If IsValid Then 
      MyLine.Cells(1, 1).Interior.ColorIndex = xlColorIndexNone 
     Else 
      MyLine.Cells(1, 1).Interior.ColorIndex = 3 'red 
     End If 

    Else 
     ' empty lines will resolve to valid, remove all color marks 
     MyLine.Cells(1, 1).EntireRow.Interior.ColorIndex = xlColorIndexNone 
    End If 

End Sub 

unterstützt Subs/Funktionen im Modul CommonFunctions, die aus aufgerufen werden Der obige Code

' module CommonFunctions 
Sub TrappedPaste() 
    If ActiveSheet.ProtectContents Then 
     ' as long as sheet is protected, we don't paste at all 
     MsgBox "Sheet is protected, all Paste/PasteSpecial functions are disabled." & vbCrLf & _ 
       "At your own risk you may unprotect the sheet." & vbCrLf & _ 
       "When unprotected, all Paste operations will implicitely be done as PasteSpecial/Values", _ 
       vbOKOnly, "Paste" 
    Else 
     ' silently do a PasteSpecial/Values 
     On Error Resume Next ' trap error due to empty buffer or other peculiar situations 
     Selection.PasteSpecial xlPasteValues 
     On Error GoTo 0 
    End If 
End Sub 

' module CommonFunctions 
Sub SetProtectionMode(MySheet As Worksheet, ProtectionMode As Boolean) 
' care for consistent protection 
    If ProtectionMode Then 
     MySheet.Protect DrawingObjects:=True, Contents:=True, _ 
         AllowSorting:=True, AllowFiltering:=True 
    Else 
     MySheet.Unprotect 
    End If 
End Sub 

' module CommonFunctions 
Function ContainsData(MyLine As Range, NOfCol As Integer) As Boolean 
' returns TRUE if any field between 1 and NOfCol is not empty 
Dim Idx As Integer 

    ContainsData = False 
    For Idx = 1 To NOfCol 
     If MyLine.Cells(1, Idx) <> "" Then 
      ContainsData = True 
      Exit For 
     End If 
    Next Idx 
End Function 

Eine wichtige Sache ist die Selection_Change. Wenn das Blatt geschützt ist, möchten wir die Zeile validieren, die der Benutzer gerade verlassen hat. Daher müssen wir die Zeilennummer, von der wir gekommen sind, verfolgen, da sich der TARGET-Parameter auf die NEUE Auswahl bezieht.

Wenn ungeschützt, könnte der Benutzer in die Kopfzeilen springen und herumspielen (obwohl es Zellensperren gibt, aber ....), also lassen wir ihn nicht den Cursor dort platzieren.

' objects in Sheet_X 
Dim Sheet_X_CurLine As Long 

Private Sub Worksheet_SelectionChange(ByVal Target As Range) 
    ' trap initial move to sheet 
    If Sheet_X_CurLine = 0 Then Sheet_X_CurLine = Target.Row 

    ' don't let them select any header row  
    If Target.Row <= T_Sheet_X.NofHRows Then 
     Me.Cells(T_Sheet_X.NofHRows + 1, Target.Column).Select 
     Sheet_X_CurLine = T_Sheet_X.NofHRows + 1 
     Exit Sub 
    End If 

    If Me.ProtectContents And Target.Row <> Sheet_X_CurLine Then 
     ' if row is changing while protected 
     ' validate old row 
     Application.ScreenUpdating = False 
     SetProtectionMode Me, False 
     Sheet_X_ValidateRow Me.Rows(Sheet_X_CurLine), True ' verbose validation 
     SetProtectionMode Me, True 
     Application.ScreenUpdating = True 
    End If 

    ' in any case make the new row current 
    Sheet_X_CurLine = Target.Row 
End Sub 

Es gibt einen Worksheet_Change Code als auch in Sheet_X, wo ich dynamisch Werte in die Drop-Down-Listen der Felder der aktuellen Zeile auf der Eingabe von anderen Zellen auf Basis laden. Da diese sehr spezifisch ist, stelle ich nur den Rahmen hier, Important vorübergehend Ereignisverarbeitung auszusetzen rekursive Aufrufe zu ändern Trigger

Private Sub Worksheet_Change(ByVal Target As Range) 
Dim IsProtected As Boolean 

    ' capture current status 
    IsProtected = Me.ProtectContents 

    If Target.Row > T_FR.NofHRows And IsProtected Then ' don't trigger anything in header rows or when protection is turned off 

     SetProtectionMode Me, False   ' because the trigger will change depending fields 
     Application.EnableEvents = False ' suspend event processing to prevent recursive calls 

     Select Case Target.Column 
      Case T_Sheet_X.CtyCode 
       ' load cities applicable for country code entered 
     ' Case T_Sheet_X. ... other stuff 
     End Select 

     Application.EnableEvents = True ' continue event processing 
     SetProtectionMode Me, True 
    End If 
End Sub 

das ist es zu vermeiden .... hoffe, dieser Beitrag sehr nützlich für einige von Ihnen ist Jungs

Viel Glück MikeD

+0

wow. interessant. +1, um sich durch dieses Validierungsminenfeld zu kämpfen. zusätzliche Anerkennung. –

+0

geehrt fühlen ... hoffe, Sie können die oben genannten selbst verwenden – MikeD

3

Ich habe ein ähnliches Projekt, wo ich die Paste Ereignis Trapping gegriffen und zwingt einen Paste nur Werte. Dadurch bleiben die Formatierung und die bedingte Formatierung/Datenüberprüfung erhalten, der Benutzer kann jedoch Werte einfügen. Dadurch wird jedoch die Möglichkeit zum Rückgängigmachen des Einfügens zerstört.

+1

+1 ... siehe Kommentar bei egarcia Posting – MikeD

4

Ich glaube, es ist möglich, das "Einfügen" Ereignis zu erfassen. Ich erinnere mich nicht an die Syntax, aber es wird Ihnen ein "Array von Zellen" geben, die kopiert werden sollen, sowie die Zelle oben links, wo die Zellen kopiert werden.

Wenn Sie eine Zelle der Wert in vba ändern müssen Sie nicht über die Validierungen überhaupt deaktivieren - so was würde ich tun ist (sorry, Pseudo-Code ist mein VBA ein wenig eingerostet)

OnPaste(cells, x, y) 
    for each cell in cells do 
    obtain the destinationCell (using the coordinates of cell on Cells, plus x and y) 
    check if the value in cell is "valid" with destinationCell's validations 
    if not valid, alert a message 
    if valid, destinationCell.value = cell.value 
    end 
end 
+0

+1 für Sie und Guitarthrower auf der taktischen Seite. Leider ist das Einfügen Ereignis als solches nicht einfangbar, Aber beide Tipps gaben mir den folgenden Einblick: 1) Damit das Einfügen korrekt funktioniert, darf der Selection_Change Trigger das Blatt nicht ändern - also habe ich eine Bedingung gestellt, die den Schutzstatus des Blattes verlangt 2) WorkBook_Activate() entfernt die Funktion Einfügen aus Menü und Kontext Menü und Traps Ctrl-v Schlüssel zu einer leeren Prozedur - PASTE-Funktion entfernen und mit PASTESPECIAL bleiben; während Workbook_Deactivate() das ursprüngliche Verhalten wiederherstellt. UNDO für PASTESPECIAL ist betroffen. – MikeD

+0

eleganter: Commandbars ("Edit") und. ("Cell") (= Kontextmenü) ... .Controls ("Paste"). OnAction = "TrappedPaste" PLUS Application.OnKey "^ v", "TrappedPaste" PLUS Sub TrappedPaste() zeigt eine MsgBox an, die alle PASTE-Versuche abtastet – MikeD

+0

Controls ("Paste Special ...") für die Balken "Edit" und "Cell", die jetzt in Trap enthalten sind, so dass clevere Benutzer PasteSpecial/All nicht verwenden können. Die TrappedPaste() führt nun einfach einen "Selection.PasteSpecial xlPasteValues" aus. Dies sollte den vorgeschlagenen "Trap Paste" vervollständigen. – MikeD

1

ich persönlich denke, dass hat eine schlechte Ideen- und oft, beispielsweise rückgängig gemacht werden, wie der Bruch Folgen unbeabsichtigte grundsätzlich mit cut and paste Funktionalität in Excel durcheinander. Da es möglich ist, die Datenvalidierung über Code hinzuzufügen, warum sollte man sie nicht einfach nach dem Einfügen wieder zum fraglichen Blatt hinzufügen? Das würde dann auch Ihr zufälliges Problem des Einfügens von Zeilen usw. lösen.

Ich tendiere dazu, einfache Subs zu schreiben, die diese Dinge ein- und ausschalten (zB mit einem Parameter namens "enabled", so dass er zum Ein- und Ausschalten aufgerufen werden kann) nochmal.

In dem Arbeitsblatt ändern Ereignis können Sie dann durch jede Zelle durchlaufen und die Validierung der Daten erzwingen (sagen für nicht leere Zellen, die eine Reihe von Fehlzündungen zu verhindern, wenn Sie eine neue Zeile einzufügen) und deaktivieren Sie jede Zelle eingefügt, die das fehlschlägt Validierung. Um diesen Vorgang für den Benutzer etwas freundlicher zu gestalten, fügen wir der Zelle vor dem Löschen einen Fehler hinzu und ändern die Hintergrundfarbe der Zelle, damit der Benutzer weiß, welche Bits er reparieren muss (offensichtlich mit a entsprechende "löschen alle Kommentare" Routine, um nach der nächsten Validierung zu laufen.

+0

Ich bin nicht anderer Meinung. Es gibt noch ein paar Gründe, warum ich die Benutzer nicht wollen einzufügen: Ich möchte nicht, Zellenformate kommen (Farben, Linien, Schriften etc.), keine Formeln, die externe Verknüpfungen erstellen können, usw. Meine Benutzer können Seien Sie in dieser Hinsicht sehr unvorsichtig – MikeD

+0

Ich stimme auch mit Runonthespot überein. Mischen mit Cut'n'Paste sollte ein definitiver letzter Ausweg sein. –

+0

Ich habe dieses Blatt jetzt seit 3 ​​Monaten in Produktion ... Widerstand war wenig, da es die Geschäftsprozesse weitaus besser abbildet als sein Vorgänger ... einige Gerüchte, weil Benutzer (ca. 200) mehr auf die Datenqualität achten müssen (Sonst Blattschläge an ihren Fingern). Datenqualität verbessert und Nachbearbeitung reduziert. stabil jetzt fast ohne technische Unterstützung benötigt. Ich hatte eine bigbig Herausforderung aber - siehe Post http://stackoverflow.com/questions/3254443 ... Dank an alle von euch diesen Beitrag zu beobachten, mir zu helfen, inspirierend und enlighting mich. BIG HUG – MikeD