Тут попросили меня друзья на работе помочь вычислить, какой гад в общем, лежащем на сервере, Excel-файле учёта работ отдела "хулиганит" - данные, ежедневно вносимые и сверяемые, задним числом подправляет. А потом общий отчёт за месяц не сводится... Вспомнил я, что The_Prist как-то выкладывал тему "Ведение журнала сделанных в книге изменений" на <EM>http://www.excel-vba.ru/index.php?file=Tips_Macro_Log</EM> Попробовал с мужиками на тестовом фауле на сервере. Здорово работает. А лист LOG я сделал VeryHidden и проект VBA запаролил от дураков. По ходу тестирования нашёл одну недоработочку - вылетает в дебаггер если выделить пустой диапазон вне UsedRange (пустую строку/столбец) или очистить строку/столбец на границе UsedRange. Ошибка возникает из-за того, что тогда получается, что Intersect(Target, Sh.UsedRange) = Nothing и цикл по ячейкам, естественно, становится невозможен. И небольшие доработки по логике и удобству работы с программой: 1 - если юзверг удаляет, например, данные из целой строки или просто большого диапазона, то формируемые циклами из старых и новых значений строковые переменные получаются такой бешенной длины, что проку от них - НОЛЬ, да и циклы по ячейкам тормозят. Поэтому я сделал ограничение накопления данных "что было"-"что стало" количеством, задаваемым в декларациях модуля величиной Const MaxCells% = 16 2 - символ-разделитель перечисляемых значений тоже задаётся в декларациях модуля параметром Const Sep$ = "; "
Доработанный файл - в приложении.
Осталась не решенной всего одна, но как высянилось, очень важная проблема: из-за работы макросов, ведущих лог, становится невозможной отмена изменений - это наикрутейший облом, т.к. "демаскирует" работу макроса! В связи с зтим есть вопрос к Дмитрию и другим макрописцам: нет ли у кого-нибудь мыслей, как сделать возможными отмены действий пользователя (не макроса!) Пытался я когда-то разобраться с макросом OnUndo (кажется, Дмитрия-The_Prist), но что-то ничего не вышло. Если кто-то найдёт время, может поможите его "присобачить" к моему примеру? _________________ 64646 !!!
Ну, поставить повышенный уровень безопасности чтобы не работали вообще все макросы книги никто и пробовать не будет, т.к. там много окошек с моими любимыми "удобными автофильтрами", а также - форма для поиска-выбора-ввода наименований и автоматом-адресов объектов (узлов связи). А если хоть один символ в паре имя-адрес не соответствует утвержденному списку, то ввод будет запрещен. Да и уровень знаний у большинства наших "орлов" такой, что в Ворде выравнивают пробелами, а межстрочный интервал меняют Enter-ами. В Ёкселе про Alt+Enter тоже мало кто знает - пробелами между слов переносы в ячейках лепят (ЗАДОЛБАЛИ!!!) Нужно бы, конечно, учет работ на Access перевести, но на это же время для разработки нужно... Да... Если побороть невозможность отмены пользователем СВОИХ действий, то это засада! А счастье казалось так близко... И макрос быстро нашёл (спасибо, Дмитрий за <EM>www.excel-vba.ru</EM> !) и баг легко нашёл, и "причесал"...
С уважением, Алексей(ИМХО: Excel-2003 - THE BEST!!!)
У меня возникла следующая проблема после вставки данной процедуры: при копировании формулы ее значение обновляется только, только если изменение сохранить или нажать Enter. Это крайне не удобно. Подскажите, пожалуйста, что с этим можно сделать? Самое интересное, что когда я удалила процедуру, проблема осталась.
Почему не один ни другой макрос с LOG не работает в компании с такими кодами Private Sub Workbook_Open() Application.ScreenUpdating = False Dim wsSh As Worksheet Sheets("1").Visible = -1 For Each wsSh In ThisWorkbook.Sheets If wsSh.Name <> "1" Then wsSh.Visible = 2 Next wsSh Dim hh As Worksheet ' Îòêëþ÷èòü òîðìîçà On Error Resume Next Application.ScreenUpdating = False Application.EnableEvents = False ' Îáðàáîòàòü âñå ëèñòû Const MyPassword = "Top2007" For Each hh In Sheets If hh.Name <> "LOG" Then If hh.ProtectContents Then Else hh.Unprotect Password:=MyPassword hh.EnableOutlining = True hh.Protect Password:=MyPassword, DrawingObjects:=True, Contents:=True, Scenarios:=True, _ AllowFiltering:=True, UserInterfaceOnly:=True End If End If Next For Each hh In Sheets hh.Cells(1).EntireRow.Hidden = True hh.Cells(1).EntireColumn.Hidden = True Next Application.ScreenUpdating = True Sheets("ÎÃËÀÂËÅÍÈÅ").Select End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range) Dim sOldValue As String Const Sep$ = "; " ' ðàçäåëèòåëü ïðè ïåðå÷èñëåíèè çíà÷åíèé ÿ÷ååê Const MaxCells% = 16 ' ìàêñèìàëüíîå êîëè÷åñòâî çíà÷åíèé ÿ÷ååê ïðè èõ ïåðå÷èñëåíèè '--------------------------------------------------------------------------------------- ' Procedure : Workbook_SheetChange ' Author : The_Prist (èäåÿ) & Alex_St (äîðàáîòêà) ' URL : http://www.excel-vba.ru/index.php?file=Tips_Macro_Log ' Date : ' Purpose : Âåäåíèå ËÎÃ-ôàéëà ïðîèçâåäåííûõ èçìåíåíèé íà ëèñòå LOG ' Notes : '--------------------------------------------------------------------------------------- If Sh.Name = "LOG" Then Exit Sub Dim sNewValue As String Dim lLastRow As Long Dim rCell As Range
With Sheets("LOG") lLastRow = .UsedRange.Row + .UsedRange.Rows.Count If lLastRow = .Rows.Count Then Exit Sub Application.ScreenUpdating = False: Application.EnableEvents = False .Cells(lLastRow, 1) = CreateObject("wscript.network").UserName .Cells(lLastRow, 2) = Format(Now, "dd.mm.yyyy HH:MM:SS") .Cells(lLastRow, 3) = Sh.Name .Cells(lLastRow, 4) = Target.Address(0, 0) .Cells(lLastRow, 5) = sOldValue If Not Intersect(Target, Sh.UsedRange) Is Nothing Then If Target.Count > 1 Then For Each rCell In Intersect(Target, Sh.UsedRange) If Not IsError(Target) Then sNewValue = sNewValue & Sep & rCell Else sNewValue = sNewValue & Sep & "Err" If UBound(Split(sNewValue, Sep)) > MaxCells - 1 Then sNewValue = sNewValue & Sep & "...": Exit For Next rCell sNewValue = Mid(sNewValue, Len(Sep) + 1) Else If Not IsError(Target) Then sNewValue = Target.Value Else sNewValue = "Err" End If End If .Cells(lLastRow, 6) = sNewValue End With Application.ScreenUpdating = True: Application.EnableEvents = True End Sub Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) Dim sOldValue As String Const Sep$ = "; " ' ðàçäåëèòåëü ïðè ïåðå÷èñëåíèè çíà÷åíèé ÿ÷ååê Const MaxCells% = 16 ' ìàêñèìàëüíîå êîëè÷åñòâî çíà÷åíèé ÿ÷ååê ïðè èõ ïåðå÷èñëåíèè Dim rCell As Range, i% sOldValue = "" If Sh.Name = "LOG" Then Exit Sub If Not Intersect(Target, Sh.UsedRange) Is Nothing Then If Target.Count > 1 Then For Each rCell In Intersect(Target, Sh.UsedRange) If Not IsError(rCell) Then sOldValue = sOldValue & Sep & rCell Else sOldValue = sOldValue & Sep & "Err" If UBound(Split(sOldValue, Sep)) > MaxCells - 1 Then sOldValue = sOldValue & Sep & "...": Exit For Next rCell sOldValue = Mid(sOldValue, Len(Sep) + 1) Else If Not IsError(Target) Then sOldValue = Target.Value Else sOldValue = "Err" End If End If End Sub Private Sub Workbook_BeforeClose(Cancel As Boolean) Dim hh As Worksheet ' Îòêëþ÷èòü òîðìîçà On Error Resume Next Application.ScreenUpdating = False Application.EnableEvents = False ' Îáðàáîòàòü âñå ëèñòû Const MyPassword = "Top2007" For Each hh In Sheets If hh.ProtectContents Then Else hh.Unprotect Password:=MyPassword End If Next For Each hh In Sheets hh.Cells(1).EntireRow.Hidden = True hh.Cells(1).EntireColumn.Hidden = True Next Application.ScreenUpdating = True Dim wsSh As Worksheet Sheets("1").Visible = -1 For Each wsSh In ThisWorkbook.Sheets If wsSh.Name <> "1" Then wsSh.Visible = 2 Next wsSh End Sub