8

在线论坛中的一个常见请求是使用代码来识别工作表中未锁定的单元格。

标准解决方案使用循环遍历活动工作表已使用部分中的每个单元格,测试每个单元格以确定它是否被锁定。下面列出了此方法的代码示例。

鉴于在循环单元格范围内固有的较差性能,有哪些更好的方法是可能的?

(注意:我确实打算添加我自己的现有方法,该方法以前托管在另一个论坛上作为一种潜在方法 - 但如果提供另一种 [合适的] 方法,我将接受它作为答案)

识别未锁定单元格的范围方法

Sub SelectUnlockedCells()
`http://www.extendoffice.com/documents/excel/1053-excel-identify-select-locked-cells.html
Dim WorkRange As Range
Dim FoundCells As Range
Dim Cell As Range
On Error GoTo SelectUnlockedCells_Error

Set WorkRange = ActiveSheet.UsedRange
For Each Cell In WorkRange
    If Cell.Locked = False Then
        If FoundCells Is Nothing Then
            Set FoundCells = Cell
        Else
            Set FoundCells = Union(FoundCells, Cell)
        End If
    End If
Next Cell
If FoundCells Is Nothing Then
    MsgBox "All cells are locked."
Else
    FoundCells.Select
End If

On Error GoTo 0
Exit Sub

SelectUnlockedCells_Error:
   MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure     
SelectUnlockedCells of Module Module1"
End Sub
4

7 回答 7

8

用于SpecialCells快速识别未锁定的单元格

下面的代码 - QuickUnlocked - 使用一种解决方法来快速生成SpecialCells错误单元格的集合,以识别未锁定的单元格范围。

关键代码步骤是:

  • 更改Application以抑制错误、代码和屏幕更新
  • 尝试解锁ActiveWorkbook和/或ActiveSheet如果它们受到保护。如果不成功退出代码
  • 制作当前工作表的副本
  • 使用删除副本中的任何现有公式错误SpecialCells
  • 保护副本工作表并覆盖错误处理,添加一个故意的公式错误,该错误只会填充未锁定的单元格
  • 清理并报告结果 重置应用程序设置

Xl2010SpecialCells之前仅限于 8192 个区域的警告

根据这篇 Microsoft 知识库文章,Excel-2007 及更早版本通过 VBA 宏最多支持 8,192 个非连续单元格。相当令人惊讶的是,将 VBA 宏应用于超过 8192 个SpecialCells Areas in these Excel versions, will not raise an error message, and the entire area under consideration will be treated as being part of theSpecialCells 的范围集合。

快速解锁码

Sub QuickUnlocked()
    Dim ws1 As Worksheet
    Dim ws2 As Worksheet
    Dim rng1 As Range
    Dim rng2 As Range
    Dim rng3 As Range
    Dim lCalc As Long
    Dim bWorkbookProtected As Boolean

    On Error Resume Next
    'test to see if WorkBook structure is protected
    'if so try to unlock it
    If ActiveWorkbook.ProtectStructure Then
        ActiveWorkbook.Unprotect
        If ActiveWorkbook.ProtectStructure Then
            MsgBox "Sorry, I could not remove the passsword protection from the workbook" _
                 & vbNewLine & "Please remove it before running the code again", vbCritical
            Exit Sub
        Else
            bWorkbookProtected = True
        End If
    End If

    Set ws1 = ActiveSheet
    'test to see if current sheet is protected
    'if so try to unlock it
    If ws1.ProtectContents Then
        ws1.Unprotect
        If ws1.ProtectContents Then
            MsgBox "Sorry, I could not remove the passsword protection from sheet" & vbNewLine & ws1.Name _
                 & vbNewLine & "Please remove it before running the code again", vbCritical
            Exit Sub
        End If
    End If
    On Error GoTo 0

    'disable screenupdating, event code and warning messages.
    'set calculation to manual
    With Application
        .ScreenUpdating = False
        .EnableEvents = False
        .DisplayAlerts = False
        lCalc = .Calculation
        .Calculation = xlCalculationManual
    End With

    On Error Resume Next
    'check for existing error cells
    Set rng1 = ws1.Cells.SpecialCells(xlCellTypeFormulas, 16)
    On Error GoTo 0

    'copy the activesheet to a new working sheet
    ws1.Copy After:=Sheets(Sheets.Count)
    Set ws2 = ActiveSheet
    'delete any cells that already contain errors
    If Not rng1 Is Nothing Then ws2.Range(rng1.Address).ClearContents

    'protect the new sheet
    ws2.Protect
    'add an error formula to all unlocked cells in the used range
    'then use SpecialCells to read the unlocked range address
    On Error Resume Next
    ws2.UsedRange.Formula = "=NA()"
    ws2.Unprotect
    Set rng2 = ws2.Cells.SpecialCells(xlCellTypeFormulas, 16)
    Set rng3 = ws1.Range(rng2.Address)
    ws2.Delete
    On Error GoTo 0

    'if WorkBook level protection was removed then reinstall it
    If bWorkbookProtected Then ActiveWorkbook.Protect

    'cleanup user interface and settings
    With Application
        .ScreenUpdating = True
        .EnableEvents = True
        .DisplayAlerts = True
        lCalc = .Calculation
    End With

    'inform the user of the unlocked cell range
    If Not rng3 Is Nothing Then
        MsgBox "The unlocked cell range in Sheet " & vbNewLine & ws1.Name & " is " & vbNewLine & rng3.Address(0, 0)
    Else
        MsgBox "No unlocked cells exist in " & ws1.Name
    End If

End Sub
于 2013-06-30T05:46:57.483 回答
5

好吧,我回到了一个循环,但我认为这种方法是有效的,因为它只引用那些Unlocked(没有选择)使用Next的单元格:

如果对象是一个范围,则此属性模拟 TAB 键,尽管该属性返回下一个单元格而不选择它。

在受保护的工作表上,此属性返回下一个未锁定的单元格。在未受保护的工作表上,此属性始终返回紧邻指定单元格右侧的单元格。

它存储第一个 (Next) Range.Address,遍历其他的,直到它返回到第一个。

Sub GetUnlockedCells_Next()
    Dim ws As Worksheet
    Dim strFirst As String
    Dim rngNext As Range
    Dim strLocked As String

    Set ws = Worksheets(1)
    ws.Protect
    Set rngNext = ws.Range("A1").Next
    strFirst = rngNext.Address
    Do
        strLocked = strLocked & rngNext.Address & ","
        Set rngNext = rngNext.Next
    Loop Until rngNext.Address = strFirst
    strLocked = Left(strLocked, Len(strLocked) - 1)     'remove the spare comma
    ws.Range(strLocked).Select
    ws.Unprotect
    MsgBox strLocked
End Sub
于 2013-06-30T21:29:02.637 回答
4

使用条件格式:- 使用公式来确定要格式化的单元格,在此公式为真的情况下格式化值:=CELL("protect",A1)=0以及应用于占用范围的选择格式?

于 2013-06-30T06:20:59.577 回答
1

我正在寻找一种方法来清除未锁定单元格的内容。问题是我的工作表有数百个(如果不是数千个)未锁定的单元格和两倍的锁定单元格。遍历它们大约需要 5-7 秒,我想要更高效的东西。

brettdj 的解决方案让我成功了一半,但是在我的范围内有这么多单元格破坏了算法。

线

Set rng3 = ws1.Range(rng2.Address)

由于 rng2 的地址超过了 256 个字符的限制,因此无法正常工作,因此 rng3 变为“无”。

我花了几个小时试图解决 256 的限制,但一无所获。在几乎放弃之后,我偶然发现了一个范围的“区域”对象。救生员!

下面调整后的代码适用于具有多个未锁定单元格的工作表。感谢 brettdj 的最初想法。

' Sub to clear unlocked cells.
Sub clearUnlockedCells()
   On Error Resume Next
   ' If the Workbook is protected, unlock it.
   Dim workbook_protected As Boolean
   If ActiveWorkbook.ProtectStructure Then
      workbook_protected = True
      ActiveWorkbook.Unprotect

      ' If we failed to unlock the Workbook, error out and exit.
      If ActiveWorkbook.ProtectStructure Then
         MsgBox "Sorry, I could not remove the passsword protection from the workbook" _
             & vbNewLine & "Please remove it before running the code again", vbCritical
         Exit Sub
      End If
   End If

   Dim source_sheet As Worksheet
   Set source_sheet = ActiveSheet
   ' If the Worksheet is protected, unlock it.
   Dim worksheet_protected As Boolean
   If source_sheet.ProtectContents Then
      worksheet_protected = True
      source_sheet.Unprotect

      ' If we failed to unlock the Worksheet, error out and exit.
      If source_sheet.ProtectContents Then
         MsgBox "Sorry, I could not remove the passsword protection from sheet" & vbNewLine & source_sheet.name _
             & vbNewLine & "Please remove it before running the code again", vbCritical
         Exit Sub
      End If
   End If

   On Error GoTo 0

   ' Disable screenupdating, event code and warning messages.
   ' Store the calculation and set it to manual.
   Dim calc As Long
   With Application
      .ScreenUpdating = False
      .EnableEvents = False
      .DisplayAlerts = False
      calc = .Calculation
      .Calculation = xlCalculationManual
   End With

   On Error Resume Next
   ' Check for existing error cells.
   Dim tmp_rng As Range
   Set tmp_rng = source_sheet.Cells.SpecialCells(xlCellTypeFormulas, 16)
   On Error GoTo 0

   ' Copy the ActiveSheet to a new working sheet.
   source_sheet.Copy After:=Sheets(Sheets.Count)
   Dim tmp_sheet As Worksheet
   Set tmp_sheet = ActiveSheet
   ' Delete any cells that already contain errors.
   If Not tmp_rng Is Nothing Then tmp_sheet.Range(tmp_rng.Address).ClearContents

   ' Protect the new sheet and add an error formula to all unlocked cells in the 
   ' used range, then use SpecialCells to read the unlocked range address.
   tmp_sheet.Protect
   On Error Resume Next
   tmp_sheet.UsedRange.Formula = "=NA()"
   tmp_sheet.Unprotect

   ' Get the range of cells with "=NA()" in them.
   Set tmp_rng = tmp_sheet.Cells.SpecialCells(xlCellTypeFormulas, 16)

   ' Iterate through the range and create a mirror of that range in the source sheet.
   Dim area As Range
   Dim source_sheet_range As Range
   Dim unlocked_cells As Range
   For Each area In tmp_rng.Areas
      Set source_sheet_range = source_sheet.Range(area.Address)
      If unlocked_cells Is Nothing Then
         Set unlocked_cells = source_sheet_range
      Else
         Set unlocked_cells = Union(unlocked_cells, source_sheet_range)
      End If
   Next area

   ' Delete the temp sheet.
   tmp_sheet.Delete

   On Error GoTo 0

   ' Protect the Workbook and Worksheet as necessary.
   If workbook_protected Then ActiveWorkbook.Protect
   If worksheet_protected Then source_sheet.Protect

   ' Cleanup user interface and settings.
   With Application
      .ScreenUpdating = True
      .EnableEvents = True
      .DisplayAlerts = True
      .Calculation = calc
   End With

   ' Clean up the unlocked cells.
   unlocked_cells.ClearContents
End Sub

希望对其他人有所帮助。如果您只想选择它们而不是清除它们,则将倒数第二行从 .ClearContents 更改为 .Select。

于 2015-01-27T20:57:17.673 回答
1

这是一个通用解决方案,它比循环遍历单元格范围要快得多,并且比克隆临时工作表等更简单、更直接。它相对较快,因为它利用了 Excel VBA 的 Find 方法的高速编译代码被实施。

Function GetUnlockedCells(SearchRange As Range) As Range 'Union
    '
    'Finds all unlocked cells in the specified range and returns a range-union of them.
    '
    'AUTHOR: Peter Straton
    '
    '*************************************************************************************************************

    Dim FoundCell As Range
    Dim FirstCellAddr As String
    Dim UnlockedUnion As Range

    'NOTE: When finding by format, you must first set the FindFormat specification:

    With Application.FindFormat
        .Clear
        .Locked = False 'This is the key to this technique
    End With

    'NOTE: Unfortunately, the FindNext method does not remember the SearchFormat:=True specification so it is
    'necessary to capture the address of the first cell found, use the Find method (instead) inside the find-next
    'loop and explicitly terminate the loop when the first-found cell is found a second time.

    With SearchRange
        Set FoundCell = .Find(What:="", After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:=xlPart, _
                              SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, _
                              SearchFormat:=True)
        If Not FoundCell Is Nothing Then
            FirstCellAddr = FoundCell.Address
            Do
'                Debug.Print FoundCell.Address
                If UnlockedUnion Is Nothing Then
                    Set UnlockedUnion = FoundCell.MergeArea                         'Include merged cells, if any
                Else
                    Set UnlockedUnion = Union(UnlockedUnion, FoundCell.MergeArea)   '           "
                End If

                Set FoundCell = .Find(What:="", After:=FoundCell, SearchDirection:=xlNext, SearchFormat:=True)
            Loop Until FoundCell.Address = FirstCellAddr
        End If
    End With
    Application.FindFormat.Clear        'Cleanup

    Set GetUnlockedCells = UnlockedUnion
End Function 'GetUnlockedCells
于 2020-07-03T00:58:14.357 回答
0

我一直在探索这一点,但我或多或少地完全了解了布雷特的方法。细微的差别是我使用当前工作表而不是创建新工作表。我最初还假设工作表中没有错误。(可以添加类似于 Brett 的代码来解决这些问题。)

我想UsedRange用“#N/A”泛滥,忽略错误,然后Application.Undo快速返回。不幸的是,我无法使用Undo(与 Word 不同)。所以我求助于使用 Variant 来抓取整个区域的数据,然后重新插入。

Sub GetUnlockedCells()
    Dim ws As Worksheet
    Dim rngUsed As Range
    Dim varKeep As Variant

    Application.ScreenUpdating = False
    Set ws = Worksheets(1)
    ws.Protect
    Set rngUsed = ws.UsedRange
    varKeep = rngUsed.Value
    On Error Resume Next
    rngUsed.Value = "#N/A"
    On Error GoTo 0
    ws.Unprotect
    MsgBox "Unlocked cells are " & _
        rngUsed.SpecialCells(xlCellTypeConstants, xlErrors).Address
    rngUsed.Value = varKeep
    Application.ScreenUpdating = True
End Sub

所以,不幸的是,我并没有超越 Brett 的代码。也许它会启发其他人,或者有人可能会发现一种使用撤消的方法;)

我也丢失了公式(转换为值),所以需要一些工作!

于 2013-06-30T20:05:05.463 回答
0

如果有很多公式,一般的方法是

For each row in ...
  lockedR = row.locked
  for each cell in row
     if isnull(lockedR) then ' inconsistent in row
        locked = cell.locked
     else 
        locked = lockedR ' consistent from row, no need to get it.

这种模式适用于许多属性,例如 HasArray。但仅对于 Locked 来说,它的速度要慢得多(100 倍)。不知道为什么这么低效。

Goto Special 将是一个可爱的技巧,但没有锁定牢房的技巧。

一个好的解决方案会很棒,但我怀疑是不可能的。

于 2017-12-18T04:43:49.597 回答