Остановить мерцание / рефакторинг кода для Excel. Обновление экрана false при копировании Вставить на другой лист - PullRequest
4 голосов
/ 21 марта 2019

Я новичок и все еще учусь программировать макросы MS Excel VBA.Мне нужна помощь сообщества, чтобы решить мою проблему с макрокодом в Excel.

    Sub export_data()

With Application
    .ScreenUpdating = False
    .Calculation = xlManual 'sometimes excel calculates values before saving files
End With

Dim wsCopy As Worksheet
Dim wsDest As Worksheet
Dim wsDest2 As Worksheet
Dim lCopyLastRow As Long
Dim lDestLastRow As Long
Dim lDestLastRow2 As Long
Dim i As Long
Dim check As Long

  'Set variables for copy and destination sheets
  Set wsCopy = Workbooks("Book 1.xlsm").Worksheets("Sheet 1")
  Set wsDest = Workbooks("Book 2.xls").Worksheets("Sheet 1")
  Set wsDest2 = Workbooks("Book 2.xls").Worksheets("Sheet 2")

  '1. Find last used row in the copy range based on data in column A
  lCopyLastRow = wsCopy.Range("J10:J16").Find(what:="", LookIn:=xlValues).Offset(-1).Row

  '2. Find first blank row in the destination range based on data in column A
  'Offset property moves down 1 row
  lDestLastRow = wsDest.Cells(wsDest.Rows.Count, "J").End(xlUp).Offset(1).Row
  lDestLastRow2 = wsDest2.Cells(wsDest2.Rows.Count, "A").End(xlUp).Offset(1).Row

  wsCopy.Unprotect "pass"

  For i = 10 To 15
  If Range("W" & i) <> "" And Range("S" & i) = "" Then
         MsgBox "please fill column S"
    GoTo protect

  ElseIf Range("K" & i) <> "" And Range("X" & i) = "" Then
         MsgBox "please fill column X"
    GoTo protect

  ElseIf Range("W" & i) <> "" And Range("Y" & i) = "" Then
         MsgBox "please fill column Y"
    GoTo protect

  ElseIf Range("W" & i) <> "" And Range("AB" & i) = "" Then
         MsgBox "please fill column AB"
    GoTo protect

  ElseIf Range("W" & i) <> "" And Range("AA" & i) = "" Then
         MsgBox "please fill column AA"
    GoTo protect

  ElseIf Range("W" & i) <> "" And Range("AC" & i) = "" Then
         MsgBox "please fill column AC"
    GoTo protect
  End If
  Next i

  If Range("W" & 10) <> "" And Range("AD" & 10) = "" Then
         MsgBox "please fill column AD"
    GoTo protect
  End If


  If WorksheetFunction.CountIf(wsDest2.Range("B10:B" & lDestLastRow2 - 1), wsCopy.Range("B10")) > 0 Then
    check = MsgBox("Double?", _
      vbQuestion + vbYesNo, "Double data")
      If check = vbYes Then
        GoTo export
      Else
        GoTo protect
      End If
   Else
        GoTo export
  End If

  If Range("Q5") <> "" Then
    check = MsgBox("sure?", _
      vbQuestion + vbYesNo, "Manual override")
      If check = vbYes Then
        GoTo export
      Else
        GoTo protect
      End If
   Else
        GoTo export
  End If


With Application
    .ScreenUpdating = True
    .Calculation = xlCalculationAutomatic
End With



export:

  '3. Copy & Paste Data
        For Each cell In wsCopy.Range("AB10:AB15")
            cell.Value = UCase(cell.Value)
        Next cell

    wsDest.Rows(lDestLastRow & ":" & lDestLastRow + lCopyLastRow - 10).Insert shift:=xlShiftDown
    wsDest.Range("A" & lDestLastRow) = WorksheetFunction.Max(wsDest.Range("A10:A" & lDestLastRow)) + 1
    wsDest.Range("L" & lDestLastRow - 1).Copy
        wsDest.Range("L" & lDestLastRow).Resize(lCopyLastRow - 9, 1).PasteSpecial Paste:=xlPasteFormulas
    wsDest.Range("R" & lDestLastRow - 1).Copy
        wsDest.Range("R" & lDestLastRow).Resize(lCopyLastRow - 9, 1).PasteSpecial Paste:=xlPasteFormulas
    wsCopy.Range("B10:K" & lCopyLastRow).Copy
        wsDest.Range("B" & lDestLastRow).PasteSpecial Paste:=xlPasteValues
    wsCopy.Range("B10:K" & lCopyLastRow).Copy
        wsDest.Range("B" & lDestLastRow).PasteSpecial Paste:=xlPasteValues
    wsCopy.Range("M10:Q" & lCopyLastRow).Copy
        wsDest.Range("M" & lDestLastRow).PasteSpecial Paste:=xlPasteValues
    wsCopy.Range("S10:AF" & lCopyLastRow).Copy
        wsDest.Range("S" & lDestLastRow).PasteSpecial Paste:=xlPasteValues


    For Each cell In wsDest.Range("B" & lDestLastRow & ":B" & lDestLastRow + lCopyLastRow - 10)
        cell.Value = wsCopy.Range("B10").Value
    Next cell

   'COPY DATA for book 2 sheet 2
    wsDest2.Rows(lDestLastRow2).Insert shift:=xlShiftDown

    wsDest2.Range("A" & lDestLastRow2) = wsDest2.Range("A" & lDestLastRow2 - 1).Value + 1

    wsCopy.Range("B10:C10").Copy
    wsDest2.Range("B" & lDestLastRow2).PasteSpecial Paste:=xlPasteValues

    wsCopy.Range("E10:Z10").Copy
    wsDest2.Range("E" & lDestLastRow2).PasteSpecial Paste:=xlPasteValues

    wsCopy.Range("AD10:AF10").Copy
    wsDest2.Range("AD" & lDestLastRow2).PasteSpecial Paste:=xlPasteValues

    Dim r As Range, tabel As Range, xTabel As Range
    Dim x As Integer, xMax As Long
    'y As Long, yMax As Long
    Dim textTabel As String
    Set tabel = wsCopy.Range("d10:d" & lCopyLastRow)
    Set r = wsDest2.Range("d" & lDestLastRow2)

    xMax = tabel.Rows.Count
    For x = 1 To xMax
        Set xTabel = tabel.Range(Cells(x, 1), Cells(x, 1))
        textTabel = Trim(xTabel.Text)
        If x = 1 Then
            textTabel = textTabel
            'r.Offset(x - 1, 0).ClearContents
        Else
            textTabel = "& " & textTabel
        End If
        r = r & textTabel
     Next x


    Dim r2 As Range, tabel2 As Range, xTabel2 As Range
    Dim x2 As Integer, xMax2 As Long
    'y As Long, yMax As Long
    Dim textTabel2 As String
    Set tabel2 = wsCopy.Range("AC10:AC" & lCopyLastRow)
    Set r2 = wsDest2.Range("AC" & lDestLastRow2)

    xMax2 = tabel2.Rows.Count
    For x2 = 1 To xMax2
        Set xTabel2 = tabel2.Range(Cells(x2, 1), Cells(x2, 1))
        textTabel2 = Trim(xTabel2.Text)
        If x2 = 1 Then
            textTabel2 = textTabel2
            'r.Offset(x - 1, 0).ClearContents
        Else
            textTabel2 = "& " & textTabel2
        End If
        r2 = r2 & textTabel2
     Next x2


    Dim r3 As Range, tabel3 As Range, xTabel3 As Range
    Dim x3 As Integer, xMax3 As Long
    'y As Long, yMax As Long
    Dim textTabel3 As String
    Set tabel3 = wsCopy.Range("AA10:AA" & lCopyLastRow)
    Set r3 = wsDest2.Range("AA" & lDestLastRow2)

    xMax3 = tabel3.Rows.Count
    For x3 = 1 To xMax3
        Set xTabel3 = tabel3.Range(Cells(x3, 1), Cells(x3, 1))
        textTabel3 = Trim(xTabel3.Text)
        If x3 = 1 Then
            textTabel3 = textTabel3
            'r.Offset(x - 1, 0).ClearContents
        Else
            textTabel3 = "& " & textTabel3
        End If
        r3 = r3 & textTabel3
     Next x3


    Dim r4 As Range, tabel4 As Range, xTabel4 As Range
    Dim x4 As Integer, xMax4 As Long
    'y As Long, yMax As Long
    Dim textTabel4 As String
    Set tabel4 = wsCopy.Range("AB10:AB" & lCopyLastRow)
    Set r4 = wsDest2.Range("AB" & lDestLastRow2)

    xMax4 = tabel4.Rows.Count
    For x4 = 1 To xMax4
        Set xTabel4 = tabel4.Range(Cells(x4, 1), Cells(x4, 1))
        textTabel4 = Trim(xTabel4.Text)
        If x4 = 1 Then
            textTabel4 = textTabel4
            'r.Offset(x - 1, 0).ClearContents
        Else
            textTabel4 = "& " & textTabel4
        End If
        r4 = r4 & textTabel4
     Next x4


  'Optional - Select the destination sheet
   wsDest.Activate
   GoTo protect


protect:
  wsCopy.protect "pass", _
    AllowFormattingCells:=True, _
    DrawingObjects:=True, _
    contents:=True, _
    Scenarios:=True

    Workbooks("Book 2.xls").Save
    Exit Sub


End Sub

Я использую Microsoft Office 2016. Когда я запускаю код, он работает хорошо, но все еще мигает.Это мешает, и я боюсь, что это замедлит обработку.

Есть идеи, как остановить мерцание во время выполнения кода?

Ответы [ 2 ]

4 голосов
/ 22 марта 2019

Сначала самое простое:

Если вы собираетесь заниматься разработкой VBA, изучите Rubberduckvba.com Это дополнение, которое значительно упростит кодирование и научит васмного вы не знали, вы хотели бы знать.Полное раскрытие Я являюсь участником этой группы.

Option Explicit не отображается в вашем коде.Кроме того, поскольку в вашем коде экспорта есть необъявленная переменная cell, я предполагаю, что она не включена по умолчанию.В верхнем меню Сервис> Параметры> вкладка «Редактор»> группа «Настройки кода»> Требуется объявление переменной, установите этот флажокЭто требует от вас Dim cell As Range, прежде чем вы сможете использовать переменную.Если опция включена, вы получите ошибку компиляции Переменная не определена , прежде чем вы сможете запустить свой код.Это может показаться незначительным, но включите эту опцию, так как это избавит вас от головной боли позже.

Вы используете check как результат окна сообщения.Не объявляйте его как Long, скорее объявляйте его Dim check As VbMsgBoxResult таким образом, когда вы наберете check=, вы получите intellisense и доступные вам значения перечисления.

Вы использовали ""в качестве заполнителя для пустой строки.Используйте vbNullString вместо этого.Это встроенная константа, которая позволяет вам знать, что эта проверка была преднамеренной.Это потому, что "" может или может быть строкой со значением "CheckValue", в котором слово удалено, оставляя только пустые кавычки.vbNullString однозначно.

Я оставил большинство имен ваших переменных вместе, чтобы вы могли легче следовать реорганизации, которую я сделал.Обратите внимание, что такие переменные, как r, x, xMax, не предоставляют никакой полезной информации о том, для чего они используются.Используйте описательные имена переменных.Будущее тебя поблагодарит.Описательные переменные делают код самодокументируемым и намного легче читать.

Комментарии.Комментарии могут быть горячей темой для некоторых людей.Я обнаружил, что с описательными переменными вам нужно меньше кода.Сам код должен сказать , что делается.Ваш комментарий "'1. Найдите последний использованный ряд ..." говорит о том, что он снова делает.lastRowInCopyArea = copyWorksheet.Range().FooBar.Row уже говорит это.Сохраните комментарии для , почему что-то сделано. Что должно быть видно из самого кода.

Венгерская нотация (HN) не нужна.Интегрированная среда разработки (IDE) может определить тип переменной в меню «Правка»> «Краткая информация» Ctrl + I .Наличие буквы, обозначающей тип, ухудшает читабельность и является переносом из предыдущих привычек кодирования.Хорошие имена переменных сами по себе исправят многое.

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


Вы используете вещи неявно.Ваш Range(Foo) неявно обращается к активному листу, на котором вы находитесь.Чтобы увидеть это, щелкните правой кнопкой мыши слово Range, чтобы вызвать контекстное меню, и выберите Definition .

. Когда вы сделаете это, вы, вероятно, получите диалоговое окно с надписью «Cannot Перейти к« Range »потому что он скрыт ", под которым теперь отображается Object Browser (зеленый).Закройте диалоговое окно, нажав кнопку «ОК».Щелкните правой кнопкой мыши в области панели «Классы» (красный) или «Участники» (синий) и выберите в контекстном меню Показать скрытых участников .

Object browser displayed

Закройте браузер объектов, щелкнув внутреннюю кнопку закрытия в верхнем правом углу, или используйте Ctrl + F4 .Ваше окно кода теперь будет отображаться.Снова вызовите контекстное меню, щелкнув правой кнопкой мыши на слове Range и выбрав Show Definition.Вы попали в скрытый класс Global и член Range.

enter image description here

В красном поле отображается имя серого класса Global, которое обычно скрыто иRange член - это то, к чему обращаются.Чтобы избежать этого неявного доступа, полностью определите свой Range с рабочим листом или ActiveSheet.Range(Foo), если вы хотите получить доступ к активному листу.Повторное выполнение этого однозначно и показывает, что оно сделано намеренно.

У нас есть левая сторона Range(Foo), теперь как насчет другой стороны? Вы также неявно обращаетесь к свойству по умолчанию. Как вы это выясните? На изображении выше, внутри оранжевого поля, слово Range имеет зеленый цвет, указывая, что это ссылка. Нажмите на него, и вы попадете в Range на панели Classes, показанной ниже. У объекта Range есть члены, к которым можно получить доступ: Methods (вещи, которые делают действие) или Properties (информация о диапазоне).

enter image description here

На панели «Члены» отображаются эти участники, к которым у вас есть доступ. Прокрутите страницу вниз до тех пор, пока не появится элемент _Default. Если вы не включили членский доступ IE Range(Foo), то вы получаете доступ к _Default члену. Поскольку вы проверяете значение ячейки, используйте Range(Foo).Value2 для определения вашего членского доступа.


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

    Dim r As Range, tabel As Range, xTabel As Range
    Dim x As Integer, xMax As Long
    'y As Long, yMax As Long
    Dim textTabel As String
    Set tabel = wsCopy.Range("d10:d" & lCopyLastRow)
    Set r = wsDest2.Range("d" & lDestLastRow2)

    xMax = tabel.Rows.Count
    For x = 1 To xMax
        Set xTabel = tabel.Range(Cells(x, 1), Cells(x, 1))
        textTabel = Trim(xTabel.Text)
        If x = 1 Then
            textTabel = textTabel
            'r.Offset(x - 1, 0).ClearContents
        Else
            textTabel = "& " & textTabel
        End If
        r = r & textTabel
     Next x

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

Что делает ваш код? Он объединяет ячейки в диапазоне, чтобы создать текстовую метку. Давайте начнем с этого для имени ConcatenateLabelFrom. Я видел, что ваша переменная r назначается каждый раз в цикле. Вам не нужно делать это, только после того, как вся конкатенация сделана. Помните, что это будет диапазон, который используется для пункта назначения. Логика цикла может быть сокращена до

Private Function ConcatenateLabelFrom(ByVal concatenateArea As Range) As String
    Dim rowInArea As Integer
    For rowInArea = 1 To concatenateArea.Rows.Count
        Dim textTabel As String
        textTabel = Trim(concatenateArea.Cells(rowInArea).Text)
        If rowInArea = 1 Then
            textTabel = textTabel
        Else
            textTabel = textTabel & "& " & textTabel
        End If
    Next

    ConcatenateLabelFrom = textTabel
End Function

Функция вызывается с помощью аргумента параметра следующим образом. Отступы предназначены только для удобства чтения.

    wsDest2.Cells(lDestLastRow2, "d").Value2 = ConcatenateLabelFrom( _
                                                    wsCopy.Range( _
                                                        wsCopy.Cells(10, "d"), _
                                                        wsCopy.Cells(lCopyLastRow, "d") _
                                                    ) _
                                                )

Ваши прыжки с GoTo не нужны. Лучше, чтобы вы реструктурировали свой код, чем прыгали с GoTo. В результате вы получите более логичный код. Также потребуется подумать о том, как вы хотите восстановить свойства Application.ScreenUpdating/Calculation.

Вы можете сделать это, заключив разделы в свои собственные подпрограммы. Ваша подпрограмма Protect будет выглядеть следующим образом и вызываться через Protect wsCopy, protectBook. И то же самое можно сделать с Экспортом.

Private Sub Protect(ByVal worksheetToProtect As Worksheet, ByVal workbookToSave As Workbook)
    worksheetToProtect.Protect "pass", _
               AllowFormattingCells:=True, _
               DrawingObjects:=True, _
               contents:=True, _
               Scenarios:=True
    workbookToSave.Save
End Sub

Ваш раздел с


Ваше мерцание экрана выглядит происходящим, потому что вы восстанавливаете обновление экрана и автоматический расчет перед экспортом. У вас там есть копия и вставка, и это то, что показывают. Помните мой комментарий о назначении r в цикле? Это часть этого. Вы можете использовать Application.Calculate , чтобы вычислить все открытые книги перед повторным включением ScreenUpdating. Как и в случае рефакторинга ваших переходов GoTo, подумайте, как вы хотите, чтобы произошла серия событий из ваших книг, и соответствующим образом кодируйте их.


Можно предложить еще кое-что, но этого должно быть достаточно для начала.

2 голосов
/ 21 марта 2019

На самом деле, использование оператора GoTo в VBA не очень хорошая практика, вам лучше разделить ваш код на несколько функций (или даже модулей), чтобы сделать весь код более читабельным.

Затем вы можете использовать if / then / else операторов select / case для обработки каждой части.Мерцание, вероятно, связано с тем, что вы активируете ScreenUpdating до некоторой части выполнения кода.


Этот блок кода:

With Application
    .ScreenUpdating = True
    .Calculation = xlCalculationAutomatic
End With

Должен быть запущен в самом конце.

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