Если лист защищен, большинство встроенных кнопок неактивны.
Как это сделать с помощью специальной ленты?
Как вы скажете: если лист защищен, отключите все настраиваемые вкладки/все кнопки на настраиваемых вкладках и включите их снова, если лист не защищен?
XML-фреймворк, стандартный модуль, ThisWorkbook:
' -- XML
<customUI xmlns = "http://schemas.microsoft.com/office/2009/07/customui"
onLoad = "LoadRibbon">
<ribbon>
<tabs>
<tab id = "Tabv3.1" label = "TOOLS" insertAfterMso = "TabHome">
' groups/buttons
</tab>
</tabs>
</ribbon>
</customUI>
' -- Standard Module
Option Explicit
Public RibUI As IRibbonUI
Sub LoadRibbon(Ribbon As IRibbonUI)
Set RibUI = Ribbon
RibUI.InvalidateControl "xy"
End Sub
' =========
' Callbacks
' =========
' -- ThisWorkbook
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
RibUI.InvalidateControl "xy"
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
RibUI.InvalidateControl "xy"
End Sub
Лента Framework с двумя вкладками: «Инструменты» и «Макросы»:
<customUI xmlns = "http://schemas.microsoft.com/office/2009/07/customui"
onLoad = "LoadRibbon">
<ribbon>
<tabs>
<tab id = "ToolsV1.0.0" label = "Tools" insertAfterMso = "Developer">
' built-in controls + a couple of macro buttons
</tab>
<tab id = "MacrosV4.0.0" label = "Macros" insertAfterMso = "ToolsV1.0.0">
' macro buttons
</tab>
</tabs>
</ribbon>
</customUI>
Да, в книге есть XML-файл с onLoad для создания ленты, это не надстройка. Приведенный выше код является лишь примером. На настоящей ленте есть две настраиваемые вкладки. 1. «Инструменты»: в основном встроенные элементы управления плюс несколько кнопок макросов. 2. «Макросы»: кнопки макросов. Если возможно, я хотел бы иметь описанное поведение для любой книги, содержащей ленту.


По сути, необходимо добавить getEnabled к элементам управления, которые вы хотите условно включить или отключить (в XML), а затем Invalidate соответствующие элементы управления. Я попробую показать небольшой такой пример:
<commands> для стандартного "SheetProtect"idMso, перехватив его событие OnAction:<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?>
<customUI onLoad = "RibbonLoaded_Addin" xmlns = "http://schemas.microsoft.com/office/2009/07/customui">
<commands>
<command idMso = "SheetProtect" onAction = "mySheetProtect" />
</commands>
<ribbon>
<tabs>
<tab id = "ToolsV1.0.0" label = "TOOLS" insertAfterMso = "Developer">
<group id = "GroupDemo" label = "Test group1">
<dropDown id = "TestDrD"
label = "Test dropDown:"
getEnabled = "Test_getEnabled"/>
</group>
</tab>
<tab id = "MacrosV4.0.0" label = "Macros" insertAfterMso = "ToolsV1.0.0">
<group id = "GroupDemo2" label = "Test group2"
imageMso = "AddInManager">
<checkBox id = "myCheckbox"
getEnabled = "Chk_getEnabled" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
MyRibbon в случае его потери (в случае ошибок VBA):Option Explicit
'To memorize the Ribbon object! ___________
#If VBA7 Then
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal length As LongPtr)
#Else
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
'_________________________________________
Public myRibbon As IRibbonUI
Const strRib As String = "\MyRibbX" 'where to save the pointer...
Public boolEnableChk As Boolean, boolDrD As Boolean 'to Disable/Enable
Скопируйте также следующие процедуры в этот стандартный модуль: а. Для запуска на загруженной ленте (запомните указатель ленты и изначально установите необходимые элементы управления как «Включено»):
Sub RibbonLoaded_Addin(ribbon As IRibbonUI)
Dim Path As String: Path = Environ("temp") & strRib
Dim File As Integer: File = FreeFile
Open Path For Output As #File
Print #File, ObjPtr(ribbon) 'memorize IRibbonUI pointer
Close #File
Set myRibbon = ribbon
boolEnableChk = True: boolDrD = True 'to initialy enable controls
myRibbon.InvalidateControl "myCheckbox" 'to update it
myRibbon.InvalidateControl "TestDrD"
End Sub
б. Субмарина способна восстановить потерянный объект «Лента»:
Sub getRibbon() 'reSet myRibbon if it was lost (it's Nothing):
Dim Path As String: Path = Environ("temp") & strRib
Dim File As Integer: File = FreeFile
Dim ribValue As String
If myRibbon Is Nothing Then
Open Path For Input As #File
Input #File, ribValue
Close #File
#If VBA7 Then
CopyMemory myRibbon, CLngPtr(ribValue), 8 'place in memory IRibbonUI object from its memorized pointer '64 bit
#Else
CopyMemory myRibbon, CLng(ribValue), 4 'place in memory IRibbonUI object from its memorized pointer
#End If
End If
End Sub
в. Два необходимых getEnabled события:
Sub Chk_getEnabled(control As IRibbonControl, ByRef enabled)
enabled = boolEnableChk
End Sub
Sub Test_getEnabled(control As IRibbonControl, ByRef enabled)
enabled = boolDrD
End Sub
д. Два (единственных) тестовых сабвуфера, чтобы увидеть, как элементы управления на ленте поочередно изменяются при игре с ними?
Sub testMakeEn_Dis_ChkBox() 'pressing alternatively it will enable/disable the check box
boolEnableChk = Not boolEnableChk
If myRibbon Is Nothing Then getRibbon
myRibbon.InvalidateControl ("myCheckbox")
End Sub
Sub testMakeEn_Dis_DropD() 'pressing alternatively it will enable/disable the DropDown
boolDrD = Not boolDrD
If myRibbon Is Nothing Then getRibbon
myRibbon.InvalidateControl ("TestDrD")
End Sub
е. И следующий Sub, способный заменить вышеупомянутые тестовые, имеющий параметры для определения имени элемента управления: Enabled\Disabled:
Sub Invalidate_Control(controlName As String) 'to be used externaly
If myRibbon Is Nothing Then getRibbon
myRibbon.InvalidateControl controlName
End Sub
ThisWorkbook. Workbook_SheetActivate событие запускается, когда вы активируете разные листы и в соответствии с его оценкой, связанной с защитой, включает или отключает необходимые элементы управления:Option Explicit
Private Sub Workbook_SheetActivate(ByVal sh As Object)
If sh.ProtectContents Then
boolEnableChk = False
'boolDrD = False 'uncomment to also use it
Else
boolEnableChk = True
'boolDrD = True 'uncomment to also use it
End If
Invalidate_Control "myCheckbox"
'Invalidate_Control "TestDrD" 'uncomment to also use it
End Sub
Пожалуйста, оставьте отзыв после тестирования. В следующем комментарии я также пришлю проверенную рабочую тетрадь (с помощью трансферного сайта ее можно будет скачать в течение ограниченного периода времени)...
Редактировать:
Чтобы вызвать нажатие кнопки "Protect Sheet", вы должны добавить его в часть XML (между <commands> - </commands>), см. выше, а в VBA вам также необходимо скопировать следующий Sub, способный запускать/перехватывать событие кнопки «OnAction» и использовать его:
Sub mySheetProtect(ByVal control As IRibbonControl, ByRef cancelDefault)
Dim ShPr As CommandBarButton
Set ShPr = Application.CommandBars.FindControl(msoControlButton, ID:=893) 'legacy way of finding the control caption...
'control getText is not exposed...
If ShPr.Caption = "&Protect Sheet..." Then
boolEnableChk = False
boolDrD = False
Else
boolEnableChk = True
boolDrD = True
End If
Invalidate_Control "TestDrD": Invalidate_Control "myCheckbox"
cancelDefault = False 'without it protect/unprotect is cancelled...
End Sub
Пожалуйста, оставьте отзыв после тестирования.
@user23636411 Пожалуйста, загрузите здесь рабочую тетрадь, в которую я играл, чтобы протестировать вышеуказанное решение. Если что-то не понятно, не стесняйтесь спрашивать разъяснения...
FaneDuru: Только что протестировал вашу книгу в Win10/Excel2019 и OSX 10.11.6/Excel2016. В принципе это работает: если вы открываете книгу и лист защищен, оба элемента управления становятся серыми. Но: элементы управления не обновляются мгновенно, как встроенные, при нажатии «Защитить/снять защиту листа». Пример флажка: Лист1 защищен. Я нажимаю «Снять защиту листа». Флажок остается отключенным. Но если я перейду на Лист2 и обратно на Лист1, флажок будет включен. Я раскомментировал три строки в Workbook_SheetActivate. Что мне не хватает? Спасибо!
@user23636411 user23636411 Нет, вы ничего не упускаете... Так было разработано вышеуказанное решение. Я понял, что код должен отключить контроль, ЕСЛИ лист защищен. Если бы я понимал, когда лист защищен, я бы ничего не делал. Excel не предоставляет никаких событий, которые могли бы инициировать изменение защиты. Но я думаю, что смогу справиться и с этим аспектом. Я имею в виду, что раньше мне удавалось перехватить событие щелчка элемента управления панели команд. Теперь я услышал о возможности такого перепрофилирования контроля. Попробую, но не сейчас, меня нет дома...
@user23636411 user23636411 Но вы можете задать еще один вопрос, упомянув, что это требование также относится к моменту, когда защита будет завершена, и, возможно, кто-то другой решит проблему. Если нет, то я попробую решить эту проблему, когда буду дома. Я почти уверен, что справлюсь...
@user23636411 user23636411 Пожалуйста, протестируйте решение, которое я опубликовал после редактирования, и внимательно прочитайте, как вам также нужно адаптировать XML-код. Вы также можете скачать адаптированную рабочую тетрадь здесь.
FaneDuru: Благодаря обновлениям элементы управления теперь мгновенно реагируют и меняют свое состояние при нажатии кнопки «Защитить/снять защиту листа». Все пользовательские кнопки теперь ведут себя как встроенные кнопки. Все отлично работает как в Win10/Excel2019, так и в OSX10.11.6/Excel2016. +1!
@user23636411 user23636411 Не стремитесь только к эффекту... Попытайтесь понять и изучить. Это новое событие делает то, что вы недавно прояснили и описали. Но предыдущий делает то, что нужно делать при перемещении между листами. Без предыдущего кода события он будет учитывать все листы, защищенные или не защищенные в соответствии с этим последним действием события, с точки зрения поведения ваших пользовательских элементов управления.
ФанеДуру: Да. Если я правильно понял, первое событие — Workbook_SheetActivate. Это заботится об элементах управления, реагирующих на изменение листа. Если лист защищен, элементы управления отключены и наоборот. Второе событие, новое — mySheetProtect, которое изменит состояние элементов управления, если вы нажмете кнопку «Защитить/Снять защиту листа». Для этого вы изменили назначение элемента управления getText. Чтобы оба события работали вместе, вам необходимо раскомментировать три строки с комментариями в ThisWorkbook. Вкратце: элементы управления «реагируют» на две вещи: 1. смену листа 2. нажатие кнопки.
@ user23636411 1. SheetActivate означает активацию листа (когда вы его активируете), а не смену листа. А второй означает кнопку SheetProtect — это событие OnAction, к которому подключено mySheetProtect. Чтобы перепрофилировать, вам нужно сделать что-то еще при нажатии на него. Ставим cancelDefault = False и отсудим код, чтобы делать все, что вам нужно. В нашем случае событие перехватывается, используется, а затем позволяется делать то, что оно обычно делает...
Теоретически это можно сделать, но это зависит от некоторых проблем: 1. Является ли книга, содержащая приведенный выше код (и XML для создания пользовательской вкладки ленты), надстройкой? Если нет, хотите ли вы, чтобы описанное выше поведение было только для соответствующей книги или нет? Во всех случаях решение будет разным. 2. Почему вы спрашиваете о «всех пользовательских вкладках», если в XML вы показываете, что создается только вкладка («ИНСТРУМЕНТЫ»)? Существуют ли другие вкладки, созданные другими книгами/надстройками? Даже в таком случае это можно было бы сделать, но решение было бы гораздо сложнее.