Excel - тактика комплексной проверки - PullRequest
6 голосов
/ 15 февраля 2010

У меня, кажется, дилемма. У меня есть шаблон EXCEL 2003, который пользователи должны использовать для заполнения табличной информации. У меня есть проверки в различных ячейках, и каждая строка проходит довольно сложную проверку VBA при событиях change и selection_change. Лист защищен для запрета операций форматирования, вставки и удаления строк и столбцов и т. Д.

Пока пользователи заполняют таблицу строка за строкой, все работает довольно хорошо. Ситуация ухудшается, если я хочу разрешить пользователю копировать / вставлять данные на этот лист (что в данном случае является законным требованием пользователя), поскольку проверка ячейки запрещает действия вставки.

Поэтому я попытался разрешить пользователям отключать защиту и вырезать / вставлять, VBA помечает лист, чтобы указать, что он содержит неподтвержденные записи. Я создал «пакетную проверку», которая проверяет все непустые строки одновременно. Тем не менее, копирование / вставка работает не слишком хорошо (необходимо напрямую переходить с исходного листа на место назначения, нельзя вставлять из текстовых файлов и т. Д.)

Проверка ячейки также не годится с точки зрения вставки строк, поскольку в зависимости от того, где вы вставляете строку, проверка ячейки может отсутствовать полностью. И если я скопирую проверки ячеек в строку 65k, размер пустого листа превысит 2M - еще один наиболее нежелательный побочный эффект.

Поэтому я подумал, что один из способов обойти проблемы - это вообще забыть о проверке ячеек и использовать только VBA. Тогда я бы пожертвовал удобством для пользователя, предоставляя раскрывающиеся списки в некоторых столбцах - некоторые из которых также меняются в зависимости от записей в других столбцах.

Кто-нибудь был в такой ситуации раньше и может дать мне несколько (общих) тактических советов (кодирование VBA не проблема)?

С уважением MikeD

Ответы [ 4 ]

4 голосов
/ 16 февраля 2010

Я полагаю, что возможно захватить событие "вставка". Я не помню синтаксис, но он даст вам «массив ячеек» для копирования, а также верхнюю левую ячейку, в которую копируются ячейки.

Если вы изменяете значение ячейки в vba, вам вообще не нужно деактивировать проверки - так что я бы сделал (извините, псевдокод, мой VBA немного ржавый)

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
3 голосов
/ 15 февраля 2010

У меня был похожий проект, в котором я прибегал к перехвату события вставки и принудительному вставке специальных значений. Это сохраняет форматирование и условное форматирование / проверку данных, но позволяет пользователю вставлять значения. Однако это уничтожает возможность отменить вставку.

1 голос
/ 14 июля 2010

Лично я считаю, что в основном шутить по поводу функциональности cut'n'paste в excel - плохая идея, и часто она имеет непреднамеренные последствия, такие как, например, прекращение отмены. Поскольку можно добавить проверку данных с помощью кода, так почему бы просто не добавить ее на лист после вставки? Тогда это также решит вашу случайную проблему вставки строк и т. Д.

Я, как правило, пишу простые сабвуферы, которые включают и выключают эти вещи (например, с помощью параметра "enabled", чтобы его можно было вызывать для выключения и повторного включения.

В событии изменения рабочего листа вы можете затем выполнить итерацию по каждой ячейке и принудительно выполнить проверку данных (скажем, для непустых ячеек, чтобы избежать пропуска зажигания при вставке новой строки) и очистить каждую вставленную ячейку, которая не прошла проверку , Чтобы сделать этот процесс более дружественным для пользователя, мы стремимся добавить комментарий к ячейке с ошибочным значением перед его очисткой и изменить цвет фона ячейки, чтобы пользователь знал, какие биты необходимо исправить (очевидно, с помощью соответствующая подпрограмма «очистить все комментарии», запускаемая после следующей проверки.

1 голос
/ 22 февраля 2010

Вот что я придумал (все в Excel 2003)

Все листы в моей книге, требующие сложной проверки, организованы в виде таблицы с парой строк заголовка, содержащих заголовок листа и заголовки столбцов. Все столбцы справа от последнего скрыты, и все строки ниже практического предела (в моем случае 200 строк) также скрыты. Я установил следующие модули:

  • GlobalDefs ... Enums
  • CommonFunctions ... функции, используемые все листы
  • Sheet_X_Functions ... функции конкретно на одном листе
  • и события запускаются в самом Sheet_X

Перечисления служат исключительно для того, чтобы избежать жесткого кодирования; Если я хочу добавить или удалить столбцы, я в основном редактирую Enums, тогда как в реальном коде я использую символические имена для каждого столбца. Это может показаться немного сложным, но я научился любить его, когда пользователи пришли в третий раз и попросили меня изменить расположение таблиц.

' 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

Сначала я опишу код, который запускается событием.

Предложения в этой теме заключались в ловушке ПАСТ. На самом деле не поддерживается триггером событий в Excel-2003, но, наконец, не большое чудо. Захват / раскрытие ПАСТЫ происходит при активизации / деактивации событий в Sheet_X. При деактивации я также проверяю статус защиты. Если нет защиты, я прошу пользователя согласиться на проверку пакета и повторно защитить. Процедуры проверки одной линии и проверки пакета являются объектами кода в модуле Sheet_X_Functions, описанном ниже.

' 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

Модуль Sheet_X_Functions в основном содержит проверочные Sub, специфичные для этого листа. Обратите внимание на использование Enum здесь - это действительно окупилось для меня - особенно в процедуре Sheet_X_ValidateRow - пользователи заставили меня изменить это ощущение 100 раз;)

' 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

поддержка вспомогательных функций / функций в модуле CommonFunctions, которые вызываются из кода выше

' 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

Одна важная вещь - это Selection_Change. Если лист защищен, мы хотим проверить строку, которую пользователь только что покинул. Поэтому мы должны отслеживать номер строки, откуда мы пришли, так как параметр TARGET ссылается на выбор NEW.

Если он не защищен, пользователь может прыгнуть в строки заголовка и начать возиться (хотя блокировки ячеек есть, но ....), поэтому мы просто не позволяем ему / ей поместить курсор там.

' 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

Также есть код Worksheet_Change в Sheet_X, где я динамически загружаю значения в раскрывающиеся списки полей текущей строки на основе ввода других ячеек. Поскольку это очень специфично, я просто представляю здесь кадр: важно временно приостановить обработку событий, чтобы избежать рекурсивных вызовов триггера изменения

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

Вот и все .... надеюсь, этот пост будет полезен для некоторых из вас, ребята

Удачи MikeD

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...