Fix (force) l oop для защиты / снятия защиты / скрытия / отображения рабочих листов - PullRequest
0 голосов
/ 02 августа 2020

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

enter image description here So this is basically how the login system works. On a worksheet named Users I created two tables. The first one is named LoginRegistry (the blue one in the image) and the second one is named Users (the green one in the image). The headers in the table Users from the column HOME to USERS have the exact same name as the worksheets on the workbook do. They are also in the exact same order. When you double click on any of the cells within that range they loop through three icons (that are just using the Webdings font colored accordingly). The green icon makes the sheet visible and editable, the blue one makes it visible but protected and the red one hides it and protects it (a bit overkill but I liked to do this just in case there are any vulnerabilities). The ALL column changes all of the permissions at once. The code for this is the following:

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)

  'Change icons when double clicking
  Set UserPermissions = Worksheets("Users").Range("Users[[ALL]:[USERS]]")
  UserPermissionsRange = UserPermissions.Address(0, 0)
  If Not Intersect(Target, Range(UserPermissionsRange)) Is Nothing Then
  
    'Change from empty to unlocked
    If Target.Value = Empty Then
      Target.Font.Color = RGB(0, 176, 80)
      Target.Value = "Ð"
      Cancel = True
    
    'Change from unlocked to read only
    ElseIf Target.Value = "Ð" Then
      Target.Font.Color = RGB(48, 84, 150)
      Target.Value = "N"
      Cancel = True
    
    'Change from read only to locked
    ElseIf Target.Value = "N" Then
      Target.Font.Color = RGB(255, 0, 0)
      Target.Value = "Ï"
      Cancel = True
    
    'Change from locked to unlocked
    ElseIf Target.Value = "Ï" Then
      Target.Font.Color = RGB(0, 176, 80)
      Target.Value = "Ð"
      Cancel = True
      
    End If
    
  Else
    Exit Sub
  
  End If
  
  'Modify all of the permissions at once
  If Target.Column = 10 Then
    AllWorksheetPermissions = "K" & Target.Row & ":" & "R" & Target.Row
    Range(AllWorksheetPermissions) = Target.Value
    Range(AllWorksheetPermissions).Font.Color = Target.Font.Color
    
  End If

End Sub

The userform that captures the information when logging in is named frmLoginForm. Each time the workbook opens the following code is executed:

Private Sub Workbook_Open()

  'Hide anything other than the form from the user
  Application.Visible = False
  'Show the worksheet that has all the information of the user permissions
  Sheets("Users").Visible = -1
  'Show login form to capture the user information
  frmLoginForm.Show

End Sub

This way the user can not do anything before authenticating. The form has two textboxes, two buttons and three labels. It is the following:

введите описание изображения здесь

Код формы:

Private Sub UserForm_Initialize()

  'Set custom colors to the form objects
  frmLoginForm.BackColor = RGB(240, 235, 215)
  cmdLogin.BackColor = RGB(201, 34, 23)
  cmdExit.BackColor = RGB(201, 34, 23)
  cmdLogin.ForeColor = vbWhite
  cmdExit.ForeColor = vbWhite

End Sub


Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

  'Close the workbook whether the user closes the form with the "Exit button" or
  'clicking the "X" icon in the top right corner
  If CloseMode = vbFormControlMenu Then
    Cancel = True
    Unload frmLoginForm
    ThisWorkbook.Close

  End If

End Sub


Private Sub cmdLogin_Click()

  'Look for the valid user range
  Set UserRangeLookUp = Worksheets("Users").Range("Users[[USER]:[PASSWORD]]")
  
  On Error Resume Next
  'Find correct password
  CorrectPassword = Application.WorksheetFunction.VLookup(tbxUser.Value, UserRangeLookUp, 2, 0)
  
  If Err.Number = 1004 Then
  MsgBox "The user you entered does not exist."
  Err.Clear
  Else
  
    If StrComp(tbxPassword.Value, CorrectPassword, vbBinaryCompare) = 0 Then
      
      'Make worksheet visible
      Application.Visible = True
      
      'Only add a new row to the LoginRegistry table if it is empty
      If (Range("Users!B3").Value) = "" Then
      Else
        Worksheets("Users").ListObjects("LoginRegistry").ListRows.Add
      End If

      'Find the last row of the LoginRegistry (adding +2 to get the absolute reference)
      Set LoginRegistryRange = Worksheets("Users").Range("LoginRegistry")
      LoginRegistryLastRow = LoginRegistryRange.Rows.Count + 2
      
      'Last row of each column
      UserLastRow = "Users!" & "B" & LoginRegistryLastRow
      DateLastRow = "Users!" & "C" & LoginRegistryLastRow
      TimeLastRow = "Users!" & "D" & LoginRegistryLastRow
      
      'Save the information of the current login to the LoginRegistry 
      Range(UserLastRow).Value = tbxUser.Value
      Range(DateLastRow).Value = Format(Date, "dd/mm/yyyy")
      Range(TimeLastRow).Value = Format(Time, "hh:mm:ss")
      
      'Protect/unprotect/hide/show worksheets according to the user permissions
      Set UserListRangeLookup = Worksheets("Users").Range("Users[USER]")
      ActiveUserRow = Application.WorksheetFunction.Match(tbxUser.Value, UserListRangeLookup, 0) + 2
      
      'Loop through the columns `HOME` to `USERS` in the table `Users`. The headers containing
      'the worksheet names to reference are in the second row of this worksheet
      'Ð means unlocked and visible, N means locked and visible, Ï locked and not visible
      For i = 11 To 18
        If Cells(ActiveUserRow, i).Value = "Ð" Then
          Sheets(Cells(2, i).Value).Unprotect "123456"
          Sheets(Cells(2, i).Value).Visible = -1
        ElseIf Cells(ActiveUserRow, i).Value = "N" Then
          Sheets(Cells(2, i).Value).Protect Password:="123456"
          Sheets(Cells(2, i).Value).Visible = -1
        ElseIf Cells(ActiveUserRow, i).Value = "Ï" Then
          Sheets(Cells(2, i).Value).Protect Password:="123456"
          Sheets(Cells(2, i).Value).Visible = 2
        End If
      Next i
      
      'Show "HOME" worksheet first always
      Sheets("HOME").Activate
      
      'Close login form
      Unload frmLoginForm
    
    Else
      MsgBox "Incorrect password. Try again."
    
    End If
  
  End If


End Sub

Private Sub cmdExit_Click()

  'Code for the exit button on the form
  Unload frmLoginForm
  ThisWorkbook.Close

End Sub

Итак, как только я объясню все это, я могу сосредоточиться на своей проблеме. Дело в том, что разрешения не всегда правильные. Я не могу найти точную ситуацию, когда код получает ошибку, но иногда при входе в систему учетные данные пользователя проверяются, но в книге ничего не обновляется. Пользователь, который должен видеть только несколько листов, остается со всеми правами администратора, например, если он ранее входил в систему. И в большинстве случаев после взлома кода он не будет работать снова, пока я не изменю одно из разрешений зарегистрированного пользователя в таблице Users. Независимо от того, сколько раз я пытаюсь войти в систему, если не будет внесено никаких изменений в разрешения, они не изменятся, и рабочие листы останутся с разрешениями последнего пользователя, который смог правильно войти в систему.

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

Прямо сейчас я считаю, что l oop - моя проблема, но я просто не могу понять, что не так. L oop выглядит следующим образом (это именно то, что я показал в предыдущем фрагменте кода, я просто копирую его снова, чтобы вы могли сосредоточиться на этой части кода):

  'Loop through the columns `HOME` to `USERS` in the table `Users` (col 11 to 18). The headers
  'containing the worksheet names to reference are in the second row of this worksheet
  'Ð means unlocked and visible, N means locked and visible, Ï locked and not visible
      For i = 11 To 18
        If Cells(ActiveUserRow, i).Value = "Ð" Then
          Sheets(Cells(2, i).Value).Unprotect "123456"
          Sheets(Cells(2, i).Value).Visible = -1
        ElseIf Cells(ActiveUserRow, i).Value = "N" Then
          Sheets(Cells(2, i).Value).Protect Password:="123456"
          Sheets(Cells(2, i).Value).Visible = -1
        ElseIf Cells(ActiveUserRow, i).Value = "Ï" Then
          Sheets(Cells(2, i).Value).Protect Password:="123456"
          Sheets(Cells(2, i).Value).Visible = 2
        End If
      Next i

Я бы очень признателен за любое ваше предложение.

1 Ответ

1 голос
/ 02 августа 2020

Возможное решение

Просто пролистав код и посмотрев на выделенный l oop (в конце), возникает серьезная проблема (простая, но серьезная): вы не квалифицирует ссылки Cells(). Следовательно, он проверяет значения Cells(ActiveUserRow, i) активного листа, который, как я предполагаю, не является листом ПОЛЬЗОВАТЕЛЕЙ, который вы только что сделали видимым, когда книга была открыта. Вам не , кстати, нужно сделать этот лист видимым, и у VBA не будет проблем с доступом к нему, когда он xlSheetVeryHidden

Первое, что вы должны сделать квалификацию этих ссылок. Прокомментируйте, если это не сработает, чтобы я мог немного углубиться в ваш код.

      With ThisWorkbook.Sheets("USERS")
        For i = 11 To 18
          If .Cells(ActiveUserRow, i).Value = "Ð" Then
            Sheets(.Cells(2, i).Value).Unprotect "123456"
            Sheets(.Cells(2, i).Value).Visible = -1
          ElseIf .Cells(ActiveUserRow, i).Value = "N" Then
            Sheets(.Cells(2, i).Value).Protect Password:="123456"
            Sheets(.Cells(2, i).Value).Visible = -1
          ElseIf .Cells(ActiveUserRow, i).Value = "Ï" Then
            Sheets(.Cells(2, i).Value).Protect Password:="123456"
            Sheets(.Cells(2, i).Value).Visible = 2
          End If
        Next i
      End With

Конец решения

Обновление (следующие комментарии):

  • У меня никогда не было проблем с доступом к ячейкам на листе xlSheetVeryHidden, будь то формулы VBA или Excel. Должен сказать, что не знаю. Я тестировал это с
Sub TestHiddenSheets()
    With Sheets("Sheet1")
        .Visible = xlSheetVeryHidden
        .Cells(1, 1) = 1
        .Protect
        Debug.Print .Cells(1, 1)
        .Visible = xlSheetVisible
    End With
    With Sheets("Sheet2")
        .Visible = xlSheetHidden
        .Cells(1, 1) = 2
        .Protect
        Debug.Print .Cells(1, 1)
        .Visible = xlSheetVisible
    End With
End Sub
  • Попытайтесь использовать привычку квалифицировать все ваши диапазоны. Фактически, вам почти никогда не следует писать Range или Cells без . перед ними.

Ваш код четко структурирован, а лог c очень ясен, хорошо сделан для который. Тем не менее, позвольте мне указать на несколько возможностей улучшения

  • Обычный способ определения диапазона - Sheets("ShtName").Range("A1"), а не Range("ShtName!A1"). Хотя оба работают, первый делает ваш код более читаемым, и нет никакой двусмысленности в том, был ли диапазон квалифицирован или нет. У меня такое чувство, что именно из-за этого вашего стиля вы не обнаружили ошибку в своем коде. Как только я увидел Range и Cells в вашем коде без ., я перестал читать все остальное и просто следил за квалификацией (или отсутствием) этих диапазонов.

  • Всегда используйте перечисления, а не буквенные значения. Например, вы должны использовать Sheet.Visible = xlSheetVisible вместо Sheet.Visible = -1

  • Я поддерживаю точку зрения @TimWilliams в его комментарии. Также вместо Resume Next вы можете использовать GoTo ErrorHandler, где вы сообщаете пользователю, что имя не найдено.

  • Я бы использовал Match вместо VLookup. Таким образом, вам не придется сортировать таблицу по алфавиту.

  • Поле со списком - лучший вариант для имени пользователя, а не текстовое поле.

  • В подпрограмме cmdExit_Click() я бы также закрыл книгу.

  • Старайтесь, насколько это возможно, не жестко кодировать значения в вашем коде. Такие значения, как "Ð", RGB(,,), пароли должны быть либо локальными / publi c переменными, либо даже перечислениями.

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