Использование глобальных переменных и локальных переменных в разных подпрограммах - PullRequest
1 голос
/ 09 мая 2019

Я копирую данные из Excel в PowerPoint, используя VBA. Это работает, но мне нужно очистить мой код. Проблема в том, что я понятия не имею, что такое хорошая практика, когда речь идет о переменных (или объектах), которые используются в более чем одной процедуре. Я думаю, что мне не хватает фундаментального понимания в этом отношении.

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

    Sub 1()
        Dim a As Long, b As Long
        Call Sub2(a, b)
        '...
    End Sub
    
    Sub2(a As Long, b As Long)
        '...
    End Sub
    

    Должен ли Sub2 вызывать переменные по-разному, например, x и y? Если нет, я возвращаюсь к вопросу 1): Почему вы передаете эти переменные непосредственно из Sub1 в Sub2, а не объявляете их глобально? Я получаю его, когда намереваюсь использовать исходное значение a и b в Sub1 после вызова Sub2 (поэтому в основном передаю его как ByVal на Sub2), но в моей ситуации это не дело.

  2. Есть ли причина ограничивать использование глобальных переменных? Я оставил их в своем коде как локальные, но я должен определить lRowAn, lRowData и т. Д. Глобально?

  3. Когда я должен передать переменную из подпункта в другой? В моем коде ниже, делать это с iSlides имеет смысл для меня, но не для wsEm.

    Следующее является частью моего фактического кода. Subs EmData и EmDataAn очень похожи, и я посмотрю, смогу ли я их объединить, но они очень хорошо иллюстрируют мою проблему, потому что они используют много одинаковых переменных.

    Public mySlide As PowerPoint.Slide
    Public PowerPointApp As PowerPoint.Application
    Public myPresentation As PowerPoint.Presentation
    Public MonatNum As String, JahrNum As String, MonatStr As String
    
    Sub CreateReport()    
        Dim DestinationPPT As String
        Dim iSlides As Integer
        Dim fRowAn As Long, lRowAn As Long, lRowData As Long
        Dim wbEm As Workbook
        Dim wsEm As Worksheet
    
        Set PowerPointApp = New PowerPoint.Application
        DestinationPPT = "C:\VBA\ReportTemplate.pptm"
        Set myPresentation = PowerPointApp.Presentations.Open(DestinationPPT)
    
        Set wbEm = Workbooks.Open("C:\VBA\Report.xlsx")
        Set wsEm = wbEm.Sheets("Sheet1")
    
        lRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).Row
        fRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).End(xlUp).Row + 1
    
        If lRowAn >= 127 Then
            If lRowData <= 127 Then '4 Slides, but separate Annotations from Data
                iSlides = 1
                Call EmData(wsEm, iSlides)
                Call EmDataAn(wsEm, iSlides)
            Else '4 Slides
                iSlides = 3
                Call EmData(wsEm, iSlides)
            End If
        Else '3 Slides
            Call EmData(wsEm, iSlides)
        End If
    
        Application.DisplayAlerts = False
        wbEm.Close SaveChanges:=False
        Application.DisplayAlerts = True
    
        PowerPointApp.Visible = True
        PowerPointApp.Activate
        Application.CutCopyMode = False
    End Sub
    
    
    
    Sub EmData(wsEm As Worksheet, iSlides As Integer)
        Dim i As Integer
        Dim fRowDataCalc As Long, lRowDataCalc As Long, lRowCopy As Long
        Dim rowHght As Long
        Dim rng As Range
    
        For i = 0 To iSlides
            fRowDataCalc = 4 + 40 * i + i * 1
            lRowDataCalc = 4 + 40 * (i + 1) + i * 1
    
            With wsEm
                .Range("B2:K3").Copy .Range("B500")
                .Range("B" & fRowDataCalc & ":K" & lRowDataCalc).Copy .Range("B502")
                rowHght = .Range("B3").EntireRow.Height
                .Range("B501").RowHeight = rowHght
                lRowCopy = .Cells(Rows.Count, "C").End(xlUp).Row
                Set rng = .Range("B500:K" & lRowCopy)
            End With
    
            Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
            mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & i + 1 & ")"
            Call PasteEm(mySlide, rng)
            rng.Clear
        Next i
    
    End Sub
    
    Sub EmDataAn(wsEm As Worksheet, iSlides As Integer)
        Dim lRowAn As Long, fRowAn As Long, lRowData As Long, fRowDataCalc As Long, lRowDataCalc As Long
        Dim rng As Range
        Dim rowHght As Long, lRowCopy As Long
    
        lRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).Row
        fRowAn = wsEm.Cells(Rows.Count, 3).End(xlUp).End(xlUp).Row - 1
        lRowData = wsEm.Cells(Rows.Count, 10).End(xlUp).Row
    
        iSlides = iSlides + 1
        fRowDataCalc = 4 + 40 * iSlides + iSlides * 1
        lRowDataCalc = lRowData
    
        'Last sheet with data
        With wsEm
            .Range("B2:K3").Copy .Range("B500")
            .Range("B" & fRowDataCalc & ":K" & lRowDataCalc).Copy .Range("B502")
            rowHght = .Range("B3").EntireRow.Height
            .Range("B501").RowHeight = rowHght
            lRowCopy = .Cells(Rows.Count, "C").End(xlUp).Row
            Set rng = .Range("B500:K" & lRowCopy)
        End With
    
        Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
    
        mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & iSlides + 2 & ")"
        Call PasteEm(mySlide, rng)
        rng.Clear
        'Annotations
        Set rng = wsEm.Range("B" & fRowAn & ":K" & lRowAn)
        rng.Copy
        Set mySlide = myPresentation.Slides.AddSlide(myPresentation.Slides.Count + 1, PPLayout("LayoutEmittenten"))
        mySlide.Shapes.Placeholders(1).TextFrame.TextRange.Text = "Headline (" & iSlides + 2 & ")"
        Call PasteEm(mySlide, rng)
    End Sub
    
    
    
    Sub PasteEm(mySlide As PowerPoint.Slide, rng As Range)
        Dim myShape As PowerPoint.Shape
    
        rng.Copy
        DoEvents
        mySlide.Shapes.PasteSpecial DataType:=ppPasteEnhancedMetafile ' = 2
        Set myShape = mySlide.Shapes(mySlide.Shapes.Count)
        With myShape
            .Width = 683
            .Top = 70
            .Left = 5
        End With
    End Sub
    

Дело не столько в функциональности кода, сколько в том, как использовать переменные в целом.

Ответы [ 2 ]

3 голосов
/ 09 мая 2019

Локальная переменная

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

Локальная переменная (передается как параметр)

Если вам нужно получить доступ к переменной в более чем одной процедуре / функции, то лучше передать ее следующей функции в качестве параметра. Это можно сделать ByRef (по умолчанию) или ByVal.

Sub ProcedureA()
    Dim ParamA As String
    ParamA = "AAA"
    Dim ParamB As String
    ParamB = "BBB"

    ProcedureB ParamA, ParamB

    Debug.Print ParamA 'returns 111
    Debug.Print ParamB 'returns BBB
End Sub

Sub ProcedureB(ByRef Param1 As String, ByVal Param2 As String)
    Param1 = "111" 'this will change ParamA in ProcedureA too
    Param2 = "222" 'this value will only be changed in ProcedureB
End Sub

При использовании ByRef (по ссылке) можно изменить параметр в ProcedureB и изменить его также в ProcedureA, но параметр, который передается ByVal (по значению), не изменяется в ProcedureA.

Технически это не имеет никакого значения, если вы называете переменные по-разному или используете одно и то же имя. Рекомендуется использовать имя, наиболее значимое в каждой из процедур (см. Названия переменных заголовков ниже).

На самом деле, я думаю, что это также хорошая практика - всегда указывать, является ли это ByRef или ByVal, и не использовать значение по умолчанию. При использовании значения по умолчанию вы всегда должны помнить, что по умолчанию это ByRef в VBA, но в VB.NET по умолчанию используется ByVal, что может легко запутать (по крайней мере, меня).

После окончания ProcedureA переменные больше не доступны (данные потеряны).

Глобальная переменная

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

Dim GlobalVarA As String

Sub ProcedureA()
    GlobalVarA = "AAA"
End Sub

Sub ProcedureB()
    Debug.Print GlobalVarA 'return AAA (if ProcedureA was run before)
End Sub

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

Глобальные переменные потеряют свои данные, когда Excel VBA завершится (или файл закроется).

Недостатком использования глобальных переменных в процедуре является необходимость всегда проверять ее значение, прежде чем использовать его в первый раз. Потому что, если он еще не был инициализирован, это Empty или Nothing. Например (выше) при запуске ProcedureB нельзя полагаться на то, что ProcedureA уже был запущен ранее. Поэтому вам нужно проверить значение GlobalVarA перед его использованием в ProcedureB, особенно если это объект, который вы должны проверить, если он не соответствует Nothing или вы легко столкнетесь с ошибками.

Местный против Глобального

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

Повторно использовать имена переменных

Повторное использование имен переменных в общем случае не проблема, если они объявлены локально. Но будет сложно, если вы используете одно и то же имя для глобальной и локальной переменных (тогда VBA предпочитает локальную!)

Dim VarA As String 'global

Sub ProcedureA()
    Dim VarA As String 'same name local
    VarA = "AAA" 'this uses always the local variable!
End Sub

Sub ProcedureB()
    Debug.Print VarA 'this uses the global variable and it is empty (after ProcedureA is run) 
End Sub

Как правило, очень полезно использовать только значимые имена переменных. Это означает, что вместо вызова переменных rng1 и rng2 вызовите их, например, InputRange и OutputRange. Также, если вам нужен счетчик (например, для обхода строк и столбцов), часто используются i и j, но он гораздо удобнее для чтения, если вы используете, например, iRow и iCol в качестве имен переменных.

Параметр Явный

Для принудительного определения правильной переменной я рекомендую всегда активировать Option Explicit: в редакторе VBA перейдите на Инструменты Параметры Требуется переменная Декларация . Это позволяет избежать неправильного ввода имен переменных и случайно вводить новые переменные.

1 голос
/ 09 мая 2019

1) When passing a variable from one procedure to another, is it a bad idea to use the same name in both procedures?

Существует 2 способа передачи параметров в другую процедуру, ByVal и ByRef.

По умолчанию VBA использует ByRef, делая так:

Option Explicit
Sub Test()

    Dim i As Long

    For i = 0 To 1000
        Call Tested(i)
    Next i

End Sub
Sub Tested(i As Long)

    i = i + 1

End Sub

Сведет вас с ума, потому что с первого цикла i = 0 перейдет к i = 2. Зачем? Потому что Tested() добавит 1 к i, а Next i в Test() к другому.

Как избежать этого и по-прежнему использовать ту же переменную? используйте ByVal, чтобы вы дали Tested() значение i, а изменения на Tested() не повлияют на ваш начальный цикл.

Глобальные переменные? Вы не должны использовать их, если это возможно.

Когда их использовать на моем опыте ?

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

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

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