Страницы: 1
RSS
VBA Фоновое открытие Книг в папке, кроме уже открытых, Макрос должен открывать все Книги в папке, кроме уже открытых
 
Ребята, всем привет! Помогите доработать макрос. Я его нашел на просторах интернета, добавил к своей Книге - работает, но есть нюансы.

Изначальная задача: при открытии Альфа-Книги необходимо в фоновом режиме открыть все Книги в Папке, сохранить изменения и закрыть.
Тестовый запуск показал, что файлы открываются, сохраняются и закрываются нормально. Но только до тех пор, пока не случается ситуация, когда один из файлов в Папке уже кем-то открыт. Тогда работа макроса приостанавливается с запросом сохранения данного файла.

Как бы мне сделать так, чтобы макрос пропускал файлы, если они в данный момент уже открыты?

Если для этого нужно полностью изменить конструкцию макроса - я не против. Файлов в Папке небольшое количество - около 10шт, т.е. даже без изящных циклов LOOP можно, например, просто подряд записать команды для каждого из файлов. Да, топорно, но главное, чтобы работало.

Код
Sub update()
With Application 'операции с приложением/отключаем для повышения скорости работы макроса
.ScreenUpdating = False 'обновление экрана
.DisplayAlerts = False 'вывод системных сообщений
Папка = "C:\Test\"
'------------ Excel-файлы в этой папке ------------------
Имя = Dir(Папка & "*.xlsx")
Do While Имя <> ""
With .Workbooks.Open _
(Filename:=Папка & Имя, UpdateLinks:=True)
'здесь Ваш макрос делает свое грязное дело
.Close SaveChanges:=True
End With
Имя = Dir
Loop
.ScreenUpdating = True 'обновление экрана
.DisplayAlerts = True 'вывод системных сообщений
End With
End Sub
Изменено: boberchik - 23.03.2023 11:52:00
 
Вариант. Создавать копии, работать с ними. Потом копировать файл с заменой, пропуская ошибки.
 
Нарамблерил еще немного информации. Натыкал вот такой макрос:

Код
Function BookOpenClosed(wbName As String) As Boolean
    Dim myBook As Workbook
    On Error Resume Next
        Set myBook = Workbooks(wbName)
    BookOpenClosed = Not myBook Is Nothing
End Function
Sub Primer1()
    If BookOpenClosed("Книга1.xlsx") Then
        MsgBox "Книга открыта"
    Else
        MsgBox "Книга закрыта"
    End If
End Sub


Он работает, но как его интегрировать в мою задачу - у меня мозгов не хватает пока что.
Изменено: boberchik - 23.03.2023 12:31:44
 
Цитата
написал:
Он работает, но
не работает. Этот код не избавит от запроса, если файл кем-то открыт.
 
Как проверить открыта ли книга?
В конце статьи приведен код, который должен Вам помочь. Если книга кем-то уже открыта, функция об этом скажет. Останется только пропустить ту, что открыта.
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Функцию проверил, она работает. Но не хватает ума пристроить ее внутрь цикла...
 
Цитата
boberchik написал:
Но не хватает ума
м-да...тяжко Вам придется. В статье конкретный пример приведен, только что цикла нет...
Код
Do While Имя <> ""
If IsBookOpen(Папка & Имя) = False Then
With .Workbooks.Open _
(Filename:=Папка & Имя, UpdateLinks:=True)
'здесь Ваш макрос делает свое грязное дело
.Close SaveChanges:=True
End With
End If
Имя = Dir
Loop
Изменено: Дмитрий(The_Prist) Щербаков - 24.03.2023 09:43:31
Даже самый простой вопрос можно превратить в огромную проблему. Достаточно не уметь формулировать вопросы...
 
Цитата
написал:
м-да...тяжко Вам придется. В статье конкретный пример приведен, только что цикла нет...
Дим, я ж не спец в VBA вообще, мне не придется, мне уже тяжко :). Это не моя основная работа, просто стараюсь автоматизировать свои процессы насколько это возможно. В Excel много чего могу, а вот VBA только-только начинаю изучать. Пока что на уровне "Нагуглить готовый макрос и немного подкорректировать его под свою задачу". Но я учусь :) Всю найденную инфу сохраняю. Нет предела совершенству.

За желание помочь огроменное спасибо! Я внедрил твой код в свой файл - вроде работает как надо. Буду еще тестить.

PS В качестве благодарности за готовый макрос готов закинуть на пару литров томатного сока ;) Кинь в личку контакт
 
Кстати, может быть, кому-то потребуется такую же задачу решать. Вот мой конечный результат:

update: добавил в конце еще обновление всей книги, чтобы все запросы PQ обновились: ThisWorkbook.RefreshAll
Код
'Макрос при открытии Книги открывает все файлы в Папке по очереди, обновляет связи, сохраняет и закрывает. Если файл уже открыт, то он пропускается.
Sub update()
With Application 'операции с приложением/отключаем для повышения скорости работы макроса
.ScreenUpdating = False 'обновление экрана
.DisplayAlerts = False 'вывод системных сообщений
Папка = "C:\Test\"
'------------ Excel-файлы в этой папке ------------------
Имя = Dir(Папка & "*.xlsx")
Do While Имя <> ""
If IsBookOpen(Папка & Имя) = False Then
With .Workbooks.Open _
(FileName:=Папка & Имя, UpdateLinks:=True)
'здесь Ваш макрос делает свое грязное дело
.Close SaveChanges:=True
End With
End If
Имя = Dir
Loop
.ScreenUpdating = True 'обновление экрана
.DisplayAlerts = True 'вывод системных сообщений
End With
ThisWorkbook.RefreshAll
End Sub



Function IsBookOpen(wbFullName As String) As Boolean
    Dim iFF As Integer, retval As Boolean
    iFF = FreeFile
    On Error Resume Next
    Open wbFullName For Random Access Read Write Lock Read Write As #iFF
    retval = (Err.Number <> 0)
    Close #iFF
    IsBookOpen = retval
End Function
Изменено: boberchik - 24.03.2023 11:29:21
 
Так как обновление нескольких файлов занимает какое-то время, то решил добавить возможность выбора - делать обновление файлов или просто открыть данную Книгу:
Код
'чтобы запускался при открытии Книги...
Sub Workbook_Open()

'выводим сообщение с вопросом
Dim RetVal As Long
Retry_:
RetVal = MsgBox("Обновить все связи и запросы? Потребуется около 1 минуты", _
vbYesNo + vbQuestion)
Select Case RetVal
Case vbNo
Exit Sub
Case vbYes
End Select

With Application 'операции с приложением/отключаем для повышения скорости работы макроса
.ScreenUpdating = False 'обновление экрана
.DisplayAlerts = False 'вывод системных сообщений
Папка = "C:\Test\"
'------------ Excel-файлы в этой папке ------------------
Имя = Dir(Папка & "Себестоимость*.xlsx")
Do While Имя <> ""
If IsBookOpen(Папка & Имя) = False Then
With .Workbooks.Open _
(FileName:=Папка & Имя, UpdateLinks:=True)
'здесь Ваш макрос делает свое грязное дело
.Close SaveChanges:=True
End With
End If
Имя = Dir
Loop
.ScreenUpdating = True 'обновление экрана
.DisplayAlerts = True 'вывод системных сообщений
End With
'Neimar, код ThisWorkBook.RefreshAll или ActiveWorkBook.RefreshAll обновляет всё, в том числе и запросы - и имени запросов знать не надо  ;)
'Это макроаналог нажатия кнопки "Обновить всё" на вкладке "Данные".
'Горячая комбинация кнопки — "Ctrl+Alt+F5"
ThisWorkbook.RefreshAll
End Sub



Function IsBookOpen(wbFullName As String) As Boolean
    Dim iFF As Integer, RetVal As Boolean
    iFF = FreeFile
    On Error Resume Next
    Open wbFullName For Random Access Read Write Lock Read Write As #iFF
    RetVal = (Err.Number <> 0)
    Close #iFF
    IsBookOpen = RetVal
End Function

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

Дано: есть мастер файл у него есть связи к файлам-источникам, у файлов-источников есть перекрестные связи и связи на свои файлы источники.
Не обсуждая зачем так, задача открыть все файлы-источники либо последовательно, либо одновременно, обновить их, обновить мастер-файл, сохранить и закрыть источники.

Реализовал следующее:
1. На отдельный лист мастер-файла вывел названия и пути хранения файлов-источников, поскольку они могут меняться.

2. Сделал макрос который последовательно открывает, обновляет, сохраняет и закрывает файлы-источники

Код
Sub TsetOpenFiles()
    Dim i As Long
      
    For i = 1 To 15 'Цикл с третьей по седьмую ячейку столбца В
        
        Workbooks.Open Filename:=Cells(i, 3).Value
             With ActiveWorkbook
                Application.ScreenUpdating = False
                Application.Calculation = xlCalculationAutomatic
                Application.CalculateFull
                ThisWorkbook.RefreshAll
                ActiveWorkbook.Close SaveChanges:=True
             End With
    Next



End Sub



Столкнулся с проблемой, что файлы не обновляются автоматом, получаю сообщение
"Не удалось обновить одну или несколько связей".

Хотелось бы автоматическое игнорирование ошибок и/или все-таки открывать не последовательно а все файлы сразу, чтобы они по кругу обновились.
Буду рад предложениям по доработке макроса.
Изменено: Mortem - 02.04.2026 14:38:34
 
Меняем концепцию. Открываем рекурсивно связанные книги.
Код
Option Explicit
Public fso As Object

Sub Обновить_связи()
    Dim done As Object
    Set done = CreateObject("Scripting.Dictionary")
    OpenLinkSources ActiveWorkbook, done
End Sub

Private Sub OpenLinkSources(wbInit As Workbook, done As Object)
    done(wbInit.FullName) = Empty
    Dim vLink As Variant, aLink As Variant, wbSource As Workbook
    aLink = wbInit.LinkSources(xlExcelLinks)
    If Not IsEmpty(aLink) Then
        For Each vLink In aLink
            If Not done.Exists(vLink) Then
                Set wbSource = GetWb(vLink)
                OpenLinkSources wbSource, done
            End If
        Next
    End If
End Sub

Private Function GetWb(ByVal sFull As String) As Workbook
    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FileExists(sFull) Then Exit Function
    Dim sName As String
    sName = fso.GetFileName(sFull)
    
    Dim wb As Workbook
    On Error Resume Next
    Set wb = Workbooks(sName)
    On Error GoTo 0
    If Not wb Is Nothing Then
        If LCase(wb.FullName) <> LCase(sFull) Then
            wb.Close False
            Set wb = Nothing
        End If
    End If
    If wb Is Nothing Then
        Set wb = Workbooks.Open(sFull)
    End If
    
    Set GetWb = wb
End Function
 
Добавил user friendly features.
Код
Option Explicit
Public fso As Object
'v2
Sub Обновить_связи()
    Dim openedBooks As Object
    Set openedBooks = GetOpenedBooks()
    
    Dim doneBooks As Object
    Set doneBooks = CreateObject("Scripting.Dictionary")
    
    Application.ScreenUpdating = False
    Dim Application_Calculation As XlCalculation: Application_Calculation = Application.Calculation: Application.Calculation = xlCalculationManual
    
    OpenLinkSources ActiveWorkbook, doneBooks
    Application.Calculate
    CloseJustOpenedBooks openedBooks
    
    Application.Calculation = Application_Calculation
    Application.ScreenUpdating = True
    
    MsgBox "Обновлены файлы:" & vbCrLf & vbCrLf & Join(doneBooks.Items(), vbCrLf), vbInformation, "Связи"
End Sub

Private Sub OpenLinkSources(wbInit As Workbook, doneBooks As Object)
    doneBooks(wbInit.FullName) = wbInit.Name
    Dim vLink As Variant, aLink As Variant, wbSource As Workbook
    aLink = wbInit.LinkSources(xlExcelLinks)
    If Not IsEmpty(aLink) Then
        For Each vLink In aLink
            If Not doneBooks.Exists(vLink) Then
                Set wbSource = GetWb(vLink)
                OpenLinkSources wbSource, doneBooks
            End If
        Next
    End If
End Sub

Private Function GetWb(ByVal sFull As String) As Workbook
    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FileExists(sFull) Then Exit Function
    Dim sName As String
    sName = fso.GetFileName(sFull)
    
    Dim wb As Workbook
    On Error Resume Next
    Set wb = Workbooks(sName)
    On Error GoTo 0
    If Not wb Is Nothing Then
        If LCase(wb.FullName) <> LCase(sFull) Then
            wb.Close False
            Set wb = Nothing
        End If
    End If
    If wb Is Nothing Then
        Set wb = Workbooks.Open(sFull, False, False)
    End If
    
    Set GetWb = wb
End Function

Private Function GetOpenedBooks() As Object
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        dic(wb.FullName) = Empty
    Next
    
    Set GetOpenedBooks = dic
End Function

Private Sub CloseJustOpenedBooks(openedBooks As Object)
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        If Not openedBooks.Exists(wb.FullName) Then
            wb.Close True
        End If
    Next
End Sub
 
МатросНаЗебре, вот это скорость написания кода :)
Вы руками писали или всетаки ИИ помогал?

Второй вариант открыл кучу файлов и остановился на блоке, конкретно на второй строчке и на нее ругается
Код
Private Sub OpenLinkSources(wbInit As Workbook, doneBooks As Object)
    doneBooks(wbInit.FullName) = wbInit.Name
    Dim vLink As Variant, aLink As Variant, wbSource As Workbook
    aLink = wbInit.LinkSources(xlExcelLinks)
    If Not IsEmpty(aLink) Then
        For Each vLink In aLink
            If Not doneBooks.Exists(vLink) Then
                Set wbSource = GetWb(vLink)
                OpenLinkSources wbSource, doneBooks
            End If
        Next
    End If
End Sub
 
Цитата
написал:
Вы руками писали или всетаки ИИ помогал?
Сам.
 
МатросНаЗебре, снимаю шляпу!
 
Цитата
написал:
открыл кучу файлов и остановился на блоке, конкретно на второй строчке и на нее ругается
Предположу, что затесался несохраненный файл.
Код
Option Explicit
Public fso As Object
'v3
Sub Обновить_связи()
    Dim openedBooks As Object
    Set openedBooks = GetOpenedBooks()
    
    Dim doneBooks As Object
    Set doneBooks = CreateObject("Scripting.Dictionary")
    
    Application.ScreenUpdating = False
    Dim Application_Calculation As XlCalculation: Application_Calculation = Application.Calculation: Application.Calculation = xlCalculationManual
    
    OpenLinkSources ActiveWorkbook, doneBooks
    Application.Calculate
    CloseJustOpenedBooks openedBooks
    
    Application.Calculation = Application_Calculation
    Application.ScreenUpdating = True
    
    MsgBox "Обновлены файлы:" & vbCrLf & vbCrLf & Join(doneBooks.Items(), vbCrLf), vbInformation, "Связи"
End Sub

Private Sub OpenLinkSources(wbInit As Workbook, doneBooks As Object)
    doneBooks(wbInit.FullName) = wbInit.Name
    Dim vLink As Variant, aLink As Variant, wbSource As Workbook
    aLink = wbInit.LinkSources(xlExcelLinks)
    If Not IsEmpty(aLink) Then
        For Each vLink In aLink
            If Not doneBooks.Exists(vLink) Then
                Set wbSource = GetWb(vLink)
                OpenLinkSources wbSource, doneBooks
            End If
        Next
    End If
End Sub

Private Function GetWb(ByVal sFull As String) As Workbook
    Dim wb As Workbook
    On Error Resume Next
    Set wb = Workbooks(sFull)
    On Error GoTo 0
    If Not wb Is Nothing Then
        Set GetWb = wb
        Exit Function
    End If
    
    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FileExists(sFull) Then Exit Function
    Dim sName As String
    sName = fso.GetFileName(sFull)
    
    On Error Resume Next
    Set wb = Workbooks(sName)
    On Error GoTo 0
    If Not wb Is Nothing Then
        If LCase(wb.FullName) <> LCase(sFull) Then
            wb.Close False
            Set wb = Nothing
        End If
    End If
    If wb Is Nothing Then
        Set wb = Workbooks.Open(sFull, False, False)
    End If
    
    Set GetWb = wb
End Function

Private Function GetOpenedBooks() As Object
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        dic(wb.FullName) = Empty
    Next
    
    Set GetOpenedBooks = dic
End Function

Private Sub CloseJustOpenedBooks(openedBooks As Object)
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        If Not openedBooks.Exists(wb.FullName) Then
            wb.Close True
        End If
    Next
End Sub
 
МатросНаЗебре, возможно.
Поскольку, я, к сожалению, вообще не понимаю текста вашего кода, я сильно начинающий, то и не могу судить о природе ошибки.
Но она повторилась, на той же строчке и Вы правы файлы на которые есть связи могли быть открыты в это время другими сторудниками.
 
Такой вариант.
Код
Option Explicit
Public fso As Object
'v5
Sub Обновить_связи()
    Dim openedBooks As Object
    Set openedBooks = GetOpenedBooks()
    
    Dim doneBooks As Object
    Set doneBooks = CreateObject("Scripting.Dictionary")
    
'    Application.ScreenUpdating = False
    Dim Application_Calculation As XlCalculation: Application_Calculation = Application.Calculation: Application.Calculation = xlCalculationManual
    
    OpenLinkSources ActiveWorkbook, doneBooks
    Application.Calculate
    CloseJustOpenedBooks openedBooks
    
    Application.Calculation = Application_Calculation
'    Application.ScreenUpdating = True
    
    MsgBox "Обновлены файлы:" & vbCrLf & vbCrLf & Join(doneBooks.Items(), vbCrLf), vbInformation, "Связи"
End Sub

Private Sub OpenLinkSources(wbInit As Workbook, doneBooks As Object)
    On Error Resume Next
    doneBooks(wbInit.FullName) = ""
    doneBooks(wbInit.FullName) = wbInit.Name
    On Error GoTo 0
    Dim vLink As Variant, aLink As Variant, wbSource As Workbook
    aLink = wbInit.LinkSources(xlExcelLinks)
    If Not IsEmpty(aLink) Then
        For Each vLink In aLink
            If Not doneBooks.Exists(vLink) Then
                Set wbSource = GetWb(vLink)
                OpenLinkSources wbSource, doneBooks
            End If
        Next
    End If
End Sub

Private Function GetWb(ByVal sFull As String) As Workbook
    Dim wb As Workbook
    On Error Resume Next
    Set wb = Workbooks(sFull)
    On Error GoTo 0
    If Not wb Is Nothing Then
        Set GetWb = wb
        Exit Function
    End If
    
    If fso Is Nothing Then Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FileExists(sFull) Then Exit Function
    Dim sName As String
    sName = fso.GetFileName(sFull)
    
    On Error Resume Next
    Set wb = Workbooks(sName)
    On Error GoTo 0
    If Not wb Is Nothing Then
        If LCase(wb.FullName) <> LCase(sFull) Then
            wb.Close False
            Set wb = Nothing
        End If
    End If
    If wb Is Nothing Then
        Set wb = Workbooks.Open(sFull, False, False)
    End If
    
    Set GetWb = wb
End Function

Private Function GetOpenedBooks() As Object
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        dic(wb.FullName) = Empty
    Next
    
    Set GetOpenedBooks = dic
End Function

Private Sub CloseJustOpenedBooks(openedBooks As Object)
    Dim wb As Workbook
    For Each wb In Application.Workbooks
        If Not openedBooks.Exists(wb.FullName) Then
            If Not wb.ReadOnly Then wb.Save
            wb.Close False
        End If
    Next
End Sub
Изменено: МатросНаЗебре - 02.04.2026 16:17:40 (If Not wb.ReadOnly Then wb.Save)
Страницы: 1
Читают тему
Наверх