Архив

Публикации с меткой ‘Что умеет Excel?’

Как просуммировать данные с нескольких листов, в том числе по условию

В данной статье я хочу рассказать, как можно просуммировать данные на одном листе из других листов. К примеру: на листах Январь, Февраль и Март расположены данные по продажам, а под ними итог. Допустим, это будет ячейка D7. Если структура всех таблиц одинакова(одинаковое кол-во строк, товара, различается) и Итог расположен во всех таблицах в одной ячейке, то можно воспользоваться простой формулой:

=СУММ(Январь:Март!D7)

Подобная ссылка на диапазоны называется трехмерной ссылкой. Минус в том, что таким образом можно просуммировать данные только ячеек листа, расположенных в одном и том же диапазоне.

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

Function All_SumIf(rRange As Range, rCriteria As Range, rSumRange As Range, Optional bAllSh As Boolean = True)
    Dim wsSh As Worksheet, sRange As String, sSumRange As String
    sRange = Right(rRange.Address, Len(rRange.Address) - InStr(rRange.Address, "!"))
    sSumRange = Right(rSumRange.Address, Len(rSumRange.Address) - InStr(rSumRange.Address, "!"))
    For Each wsSh In Sheets
        If bAllSh Then
            If wsSh.Name <> Application.Caller.Parent.Name Then
                All_SumIf = All_SumIf + Application.SumIf(wsSh.Range(sRange), rCriteria, wsSh.Range(sSumRange))
            End If
        Else
            If wsSh.Index < Application.Caller.Parent.Index Then
                All_SumIf = All_SumIf + Application.SumIf(wsSh.Range(sRange), rCriteria, wsSh.Range(sSumRange))
            End If
        End If
    Next wsSh
End Function
Function All_SumIf(rRange As Range, rCriteria As Range, rSumRange As Range, Optional bAllSh As Boolean = True)
    Dim wsSh As Worksheet, sRange As String, sSumRange As String
    sRange = Right(rRange.Address, Len(rRange.Address) - InStr(rRange.Address, "!"))
    sSumRange = Right(rSumRange.Address, Len(rSumRange.Address) - InStr(rSumRange.Address, "!"))
    For Each wsSh In Sheets
        If bAllSh Then
            If wsSh.Name <> Application.Caller.Parent.Name Then
                All_SumIf = All_SumIf + Application.SumIf(wsSh.Range(sRange), rCriteria, wsSh.Range(sSumRange))
            End If
        Else
            If wsSh.Index < Application.Caller.Parent.Index Then
                All_SumIf = All_SumIf + Application.SumIf(wsSh.Range(sRange), rCriteria, wsSh.Range(sSumRange))
            End If
        End If
    Next wsSh
End Function

Чтобы правильно воспользоваться кодом советую прочитать статью: Что такое функция пользователя(UDF)?

Аргументы функции аналогичны стандартной СУММЕСЛИ, только в конце добавлен еще один, необязательный.

rRange - Ссылка на диапазон ячеек. Указывается диапазон значений, среди которых необходимо искать критерий.
rCriteria - Ссылка на одну ячейку. Указывается ячейка, в которой содержится значение, данные по которому надо просуммировать.
rSumRange - Ссылка на диапазон ячеек. Указывается диапазон сумм или чисел, которые необходимо просуммировать на основании критерия.
bAllSh - Необязательный аргумент. Если не указан, или указано значение 1 или ИСТИНА, то будут суммироваться значения со всех листов, кроме листа, на котором записана функция. Если указано значение 0 или ЛОЖЬ, то будут суммироваться значения с листов, расположенных до листа, на котором записана функция.

Применение обех функций вы найдете в примере к статье.

Скачать пример »

  Tips_All_SumIf_Few_Sheets.xls (54,0 KiB, 12 скачиваний)

Как вернуть меню в стиле Excel 2003 в версиях 2007 и старше

25 Февраль 2012 2 comments

С приходом в нашу жизнь нового Excel 2007 в неё также пришли не только свыше миллиона строк, почти неограниченное количество условий условного форматирования и еще куча новых возможностей, но и новый стиль. Притом не просто новый, а совершенно переработанный, незнакомый, неузнаваемый и запутывающий. Кто-то годами пользовался 2003 Excel и настолько привык к его интерфейсу, что новый стиль был воспринят не просто в штыки — многие из этих людей до сих пор пользуются 2003 практически только из-за нежелания переходить на новый стиль.
Тем, кто боится перейти на новый интерфейс только по этой причине, а так же тем, кого мучает ностальгия по прежнему интерфейсу могу предложить несложный код, который на вкладке «Надстройки» создает ряд меню, повторяющих старый добрый 2003.

Сам код:

Sub MakeMenu2003()
    Dim objCmndBr As CommandBar, objMenu03 As CommandBar, objCtrl As CommandBarControl
    Dim avArr, li As Long
    'Удаляем меню, если оно уже создано
    On Error Resume Next
    Application.CommandBars("Меню 2003").Delete
    On Error GoTo 0
    'Для английской локализации
'    avArr = Array("&File", "&Edit", "&View", "&Insert", "F&ormat", "&Tools", "&Data", "&Window", "&Help")
    'Для русской локализации
    avArr = Array("Фа&йл", "&Правка", "&Вид", "Вст&авка", "Фор&мат", "С&ервис", "&Данные", "&Окно", "&Справка")
    'Создаем панель на которой будут контролы со старым видом меню
    Set objMenu03 = Application.CommandBars.Add("Меню 2003", , False)
    'Создаем контролы путем копирования групп меню целиком
    For li = LBound(avArr) To UBound(avArr)
        Application.CommandBars("Built-in Menus").Controls(avArr(li)).Copy objMenu03
    Next li
    'Делаем меню видимым
    Application.CommandBars("Меню 2003").Visible = True
End Sub
Sub MakeMenu2003()
    Dim objCmndBr As CommandBar, objMenu03 As CommandBar, objCtrl As CommandBarControl
    Dim avArr, li As Long
    'Удаляем меню, если оно уже создано
    On Error Resume Next
    Application.CommandBars("Меню 2003").Delete
    On Error GoTo 0
    'Для английской локализации
'    avArr = Array("&File", "&Edit", "&View", "&Insert", "F&ormat", "&Tools", "&Data", "&Window", "&Help")
    'Для русской локализации
    avArr = Array("Фа&йл", "&Правка", "&Вид", "Вст&авка", "Фор&мат", "С&ервис", "&Данные", "&Окно", "&Справка")
    'Создаем панель на которой будут контролы со старым видом меню
    Set objMenu03 = Application.CommandBars.Add("Меню 2003", , False)
    'Создаем контролы путем копирования групп меню целиком
    For li = LBound(avArr) To UBound(avArr)
        Application.CommandBars("Built-in Menus").Controls(avArr(li)).Copy objMenu03
    Next li
    'Делаем меню видимым
    Application.CommandBars("Меню 2003").Visible = True
End Sub

Учитывайте, что созданная панель будет существовать даже после закрытия файла и даже после закрытия Excel.
На всякий случай я добавил в код массив наименований меню для английской локализации офиса, т.к. не у всех может оказаться действительно русская локализация или кто-то просто принципиально пользуется английской.
Так же прилагаю файл, в котором можно нажатием кнопок создать меню и удалить. Для тех, кто незнаком с макросами: прежде чем попробовать создать меню с помощью выложенного здесь файла настоятельно рекомендую прочесть эту статью: Почему не работает макрос?

Конечно, это не лучший вариант и если поискать в сети, то можно найти программы, которые так же воссоздают меню в стиле 2003, но почти все они платные.

Скачать пример »

  Tips_All_MakeMenu2003.xls (44,5 KiB, 129 скачиваний)

Как отменить действия макроса

Рано или поздно каждый, кто программирует в VBA задается вопросом: а можно ли отменить действия, совершенные макросом? Для начала надо понять, в каких ситуациях это нам надо. Если вдруг Вы выполнили код, который испортил или удалил Ваши данные в файле. Но Вам эти данные еще нужны и Вы думаете, как же можно все вернуть. Самой простое, что можно сделать это закрыть файл без сохранения и открыть заново. Все данные будут на месте(если, конечно, в коде не было строки, сохраняющей файл). Второй способ: это перед выполнением макроса делать резервную копию файла — тогда Ваши исходные данные всегда будут целы.




Но как же сделать отмену действий макроса через стандартную кнопку на панели или сочетанием клавиш Ctrl+Z и можно ли? Ответ — можно. Но сразу вопрос: а насколько это нужно? В каких ситуациях это может пригодиться? Я вот так вот навскидку сразу не сказал бы, если бы не являлся разработчиком программ и надстроек в среде Microsoft Excel. Именно в надстройках отмена действий наиболее востребована, на мой взгляд. Например надстройка объединяет ячейки. Объединили случайно и…В стандартной ситуации после такого макроса нельзя отменить действия. Закрывать файл без сохранения? Как-то некрасиво получается, если продукт является коммерческим. И тогда приходится извращаться и пытаться сделать возможным отмену действий макроса. В моей надстройке MyAddin в некоторых командах отмена действий команд как раз и применяется. Но ближе к делу, точнее к коду:

'Создаем свой пользовательский тип данных
Type SaveRange
    vFormula As Variant
    sAddr As String
    lColor As Long
End Type
'Переменные для запоминания данных
Public wbWBook As Workbook
Public wsSh As Worksheet
Public vOldVals() As SaveRange
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    '   Сначала запоминаем значения выделенных ячеек на листе
    ReDim vOldVals(1 To Selection.Count)
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsSh = ActiveSheet
    'Запоминаем значения(заносим в массив)
    li = 1
    For Each rCell In Selection
        'запоминаем адрес ячейки
        vOldVals(li).sAddr = rCell.Address
        'запоминаем формулу(если нет формулы - значение)
        vOldVals(li).vFormula = rCell.Formula
        'запоминаем цвет заливки ячейки
        vOldVals(li).lColor = rCell.Interior.Color
        li = li + 1
    Next rCell
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub
 
'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    Dim li As Long
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'Активируем лист, в котором были сделаны изменения
    wsSh.Activate
    'Возвращаем значения
    For li = 1 To UBound(vOldVals)
        Range(vOldVals(li).sAddr).Formula = vOldVals(li).vFormula
        Range(vOldVals(li).sAddr).Interior.Color = vOldVals(li).lColor
    Next li
    Exit Sub
 
    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "Error"
End Sub
'Создаем свой пользовательский тип данных
Type SaveRange
    vFormula As Variant
    sAddr As String
    lColor As Long
End Type
'Переменные для запоминания данных
Public wbWBook As Workbook
Public wsSh As Worksheet
Public vOldVals() As SaveRange
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    '   Сначала запоминаем значения выделенных ячеек на листе
    ReDim vOldVals(1 To Selection.Count)
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsSh = ActiveSheet
    'Запоминаем значения(заносим в массив)
    li = 1
    For Each rCell In Selection
        'запоминаем адрес ячейки
        vOldVals(li).sAddr = rCell.Address
        'запоминаем формулу(если нет формулы - значение)
        vOldVals(li).vFormula = rCell.Formula
        'запоминаем цвет заливки ячейки
        vOldVals(li).lColor = rCell.Interior.Color
        li = li + 1
    Next rCell
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub

'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    Dim li As Long
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'Активируем лист, в котором были сделаны изменения
    wsSh.Activate
    'Возвращаем значения
    For li = 1 To UBound(vOldVals)
        Range(vOldVals(li).sAddr).Formula = vOldVals(li).vFormula
        Range(vOldVals(li).sAddr).Interior.Color = vOldVals(li).lColor
    Next li
    Exit Sub

    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "Error"
End Sub

Комментарии к коду я старался сделать максимально подробными, поэтому думаю, что больше нечего разъяснять. К тому по древней традиции я приложил к статье пример с данным кодом :) Единственное, что могу добавить: пользовательский тип SaveRange может быть дополнен еще какими-либо переменными, помимо vFormula, sAddr и lColor. Например цвет границ ячейки, цвет шрифта и т.д. Все зависит от того, какие изменения Вы будете делать кодом и что захотите затем вернуть.

Скачать пример »

  Tips_Restore_Macro.xls (48,5 KiB, 62 скачиваний)




Код, приведенный выше, несомненно хорош, но если кол-во изменяемых ячеек достаточно велико, то код будет очень замедлять работу. Поэтому если есть возможность добавлять/удалять листы в книгах, то можно схитрить: сделать резервную копию листа, лист сделать очень скрытым и как только потребуется отмена действия — вернуть этот лист, удалив исходный(с уже испорченными данными):

'Переменные для запоминания данных
Public wbWBook As Workbook
Public wsSh As Worksheet, wsActSh As Worksheet, sSh_Name As String, lShPoz As Long
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsActSh = ActiveSheet
    lShPoz = wsActSh.Index
    sSh_Name = wsActSh.Name
    Application.ScreenUpdating = 0
    wsActSh.Copy , wbWBook.Sheets(wbWBook.Sheets.Count)
    Set wsSh = wbWBook.Sheets(wbWBook.Sheets.Count)
    wsSh.Visible = xlVeryHidden
    wsActSh.Activate
    Application.ScreenUpdating = 1
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub
 
'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    Application.ScreenUpdating = 0
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'делаем видимым резервный лист
    wsSh.Visible = -1
    'Удаляем исходный лист, данные в котором уже изменены
    Application.DisplayAlerts = 0
    wsActSh.Delete
    Application.DisplayAlerts = 1
    'назначаем резервному листу имя исходного
    wsSh.Name = sSh_Name
    wsSh.Move wbWBook.Sheets(lShPoz)
    'Активируем резервный лист
    wsSh.Activate
    Application.ScreenUpdating = 0
    Exit Sub
    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "Error"
End Sub
'Переменные для запоминания данных
Public wbWBook As Workbook
Public wsSh As Worksheet, wsActSh As Worksheet, sSh_Name As String, lShPoz As Long
'---------------------------------------------------------------------------------------
' Procedure : Fill_Numbers
' Purpose   : Основная процедура. Это тот код, который вносит изменения на лист
'             и действия которого нам необходимо отменить
'             Процедура заполняет выделенные ячейки номерами
'             и изменяет цвет заливки
'---------------------------------------------------------------------------------------
Sub Fill_Numbers()
    Dim rCell As Range, li As Long
    'Запоминаем активную книгу
    'это на случай, если отмена действий будет производиться из другой книги
    Set wbWBook = ActiveWorkbook
    'Запоминаем активный лист
    'на случай, если отмена действий будет производиться из другого листа
    Set wsActSh = ActiveSheet
    lShPoz = wsActSh.Index
    sSh_Name = wsActSh.Name
    Application.ScreenUpdating = 0
    wsActSh.Copy , wbWBook.Sheets(wbWBook.Sheets.Count)
    Set wsSh = wbWBook.Sheets(wbWBook.Sheets.Count)
    wsSh.Visible = xlVeryHidden
    wsActSh.Activate
    Application.ScreenUpdating = 1
    '======================================
    'Выполняем основные действия(собственно тот код, который надо будет отменить)
    li = 1
    For Each rCell In Selection
        rCell = li
        rCell.Interior.ColorIndex = li
        li = li + 1
    Next rCell
    '======================================
    'Назначаем стандартному вызову отмены действий выполнение нашего макроса возвращения значений
    Application.OnUndo "Отменить заполнение ячеек номерами", "Restore_Vals"
End Sub

'---------------------------------------------------------------------------------------
' Procedure : Restore_Vals
' Purpose   : Процедура отмены действия(возврат значений)
'---------------------------------------------------------------------------------------
Sub Restore_Vals()
    'В случае непредвиденной ошибки переходим на метку
    'и показываем сообщение об ошибке
    On Error GoTo Erreble
    Application.ScreenUpdating = 0
    'Активируем книгу, в которой были сделаны изменения
    wbWBook.Activate
    'делаем видимым резервный лист
    wsSh.Visible = -1
    'Удаляем исходный лист, данные в котором уже изменены
    Application.DisplayAlerts = 0
    wsActSh.Delete
    Application.DisplayAlerts = 1
    'назначаем резервному листу имя исходного
    wsSh.Name = sSh_Name
    wsSh.Move wbWBook.Sheets(lShPoz)
    'Активируем резервный лист
    wsSh.Activate
    Application.ScreenUpdating = 0
    Exit Sub
    'Показываем сообщение о невозможности отмены действия
Erreble:
    MsgBox "Нельзя отменить действие!", vbCritical, "Error"
End Sub

Скачать пример »

  Tips_Restore_Macro_HiddenSh.xls (45,0 KiB, 50 скачиваний)

Можно много чего придумать — вплоть до сохранения и последующего извлечения резервных копий файлов.

Разделение строк с разным содержимым «зеброй»

 

Допустим у Вас есть бо-о-ольшая таблица с данными. В одном столбце таблицы находятся данные по артикулам. В остальных имеется остальные данные по ним — операции, дата/время, сумма товара и т.д. Просматривать такую таблицу глазами неудобно: каждый раз приходится сверяться — а в той ли строке я смотрю? Да и разбить данные на отдельные блоки по идентичным артикулам не мешало бы. Но как? Как разбить не подскажу, а вот раскрасить можно. Один артикул в один цвет, другой в другой. И так по очереди — то один, то другой. Зебра, одним словом. Чтоб было понятней см. рис.1.

Пример таблицырис.1

Для этого надо всего лишь создать стандартный модуль и вставить в него следующий код:

Sub Zebra()
    Dim li As Long, lColor As Long, lColNum As Long, lColEND As Long
    lColor = xlNone
    On Error Resume Next
    lColNum = InputBox("Укажите номер столбца со значениями", "Окно ввода параметра", 1)
    If lColNum = 0 Then Exit Sub
    If Not IsNumeric(lColNum) Then Exit Sub
    On Error GoTo 0
    lColEND = Cells(1, Columns.Count).End(xlToLeft).Column
    Application.ScreenUpdating = False
    For li = 2 To Cells(Rows.Count, lColNum).End(xlUp).Row
        If Cells(li, lColNum) <> Cells(li - 1, lColNum) Then
            If lColor = xlNone Then lColor = vbGreen Else lColor = xlNone
        End If
        Range(Cells(li, 1), Cells(li, lColEND)).Interior.Color = lColor
    Next li
    Application.ScreenUpdating = True
End Sub
Sub Zebra()
    Dim li As Long, lColor As Long, lColNum As Long, lColEND As Long
    lColor = xlNone
    On Error Resume Next
    lColNum = InputBox("Укажите номер столбца со значениями", "Окно ввода параметра", 1)
    If lColNum = 0 Then Exit Sub
    If Not IsNumeric(lColNum) Then Exit Sub
    On Error GoTo 0
    lColEND = Cells(1, Columns.Count).End(xlToLeft).Column
    Application.ScreenUpdating = False
    For li = 2 To Cells(Rows.Count, lColNum).End(xlUp).Row
        If Cells(li, lColNum) <> Cells(li - 1, lColNum) Then
            If lColor = xlNone Then lColor = vbGreen Else lColor = xlNone
        End If
        Range(Cells(li, 1), Cells(li, lColEND)).Interior.Color = lColor
    Next li
    Application.ScreenUpdating = True
End Sub

После вставки просто нажмите Alt+F8 и выполните макрос Zebra.

Скачать пример »

  Tips_Macro_Zebra.xls (37,0 KiB, 1 030 скачиваний)

Так же см.:
Выделение строк цветом через одну
Координатное выделение строки и столбца

Как защитить лист от пользователя, но не от макроса?

 

Иногда бывает полезно защитить данные на листе от изменений другими пользователями, но при этом так же надо будет работать с данными на листе из VBA(т.е. вносить изменения с помощью кода). Обычная защита листа, конечно, подходит, хоть и есть небольшой недостаток: надо перед каждым обращением к листу снимать с него защиту, выполнять необходимые действия и защищать заново. Но есть метод проще.
Если выполнить ниже приведенную строчку кода, то пользователю невозможно будет изменить данные на листе(кроме тех, которые Вы сами разрешите), однако код VBA(макрос) сможет преспокойно вносить любые изменения, не снимая защиту.

Sub Protect_for_User_Non_for_VBA()
    ActiveSheet.Protect Password:="1111", UserInterfaceOnly:=True
End Sub
Sub Protect_for_User_Non_for_VBA()
    ActiveSheet.Protect Password:="1111", UserInterfaceOnly:=True
End Sub

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

Sub Protect_for_User_Non_for_VBA()
    Sheets(2).Protect Password:="1111", UserInterfaceOnly:=True
    Sheets("Лист1").Protect Password:="1111", UserInterfaceOnly:=True
End Sub
Sub Protect_for_User_Non_for_VBA()
    Sheets(2).Protect Password:="1111", UserInterfaceOnly:=True
    Sheets("Лист1").Protect Password:="1111", UserInterfaceOnly:=True
End Sub

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

Sub Protect_for_User_Non_for_VBA()
    Sheets(2).Protect Password:="1111", UserInterfaceOnly:=True
    'на лист "Лист1" поставим защиту и разрешим пользоваться фильтром
    Sheets("Лист1").Protect Password:="1111", AllowFiltering:=True, UserInterfaceOnly:=True
End Sub
Sub Protect_for_User_Non_for_VBA()
    Sheets(2).Protect Password:="1111", UserInterfaceOnly:=True
    'на лист "Лист1" поставим защиту и разрешим пользоваться фильтром
    Sheets("Лист1").Protect Password:="1111", AllowFiltering:=True, UserInterfaceOnly:=True
End Sub

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

Примечание: данный макрос лучше всего прописывать на событие открытия книги(модуль ЭтаКнига(ThisWorkbook)), т.к. сразу после закрытия и открытия книги данная защита снимается.

Можно ставить защиту сразу на все листы книги при открытии таким кодом:

Private Sub Workbook_Open()
    Dim wsSh As Object
    For Each wsSh In Me.Sheets
        Protect_for_User_Non_for_VBA wsSh
    Next wsSh
End Sub
Sub Protect_for_User_Non_for_VBA(wsSh As Object)
    wsSh.Protect Password:="1111", UserInterfaceOnly:=True
End Sub
Private Sub Workbook_Open()
    Dim wsSh As Object
    For Each wsSh In Me.Sheets
        Protect_for_User_Non_for_VBA wsSh
    Next wsSh
End Sub
Sub Protect_for_User_Non_for_VBA(wsSh As Object)
    wsSh.Protect Password:="1111", UserInterfaceOnly:=True
End Sub

Данный код вставляется в модуль ЭтаКнига.

Ну и если Вам только на один лист надо установить, то убираем цикл и вызываем процедуру только для нужного листа:

Private Sub Workbook_Open()
    Protect_for_User_Non_for_VBA Me.Sheets("Лист1")
End Sub
Sub Protect_for_User_Non_for_VBA(wsSh As Object)
    wsSh.Protect Password:="1111", AllowFiltering:=True, UserInterfaceOnly:=True
End Sub
Private Sub Workbook_Open()
    Protect_for_User_Non_for_VBA Me.Sheets("Лист1")
End Sub
Sub Protect_for_User_Non_for_VBA(wsSh As Object)
    wsSh.Protect Password:="1111", AllowFiltering:=True, UserInterfaceOnly:=True
End Sub

Также см.:
Как разрешить изменять только выбранные ячейки?
Защита листов/снятие защиты
Как оставить возможность работать со структурой на защищенном листе?

Как узнать существует ли лист в книге?

 

Довольно часто при добавлении листов в книгу кодом необходимо удостовериться существует ли уже лист с таким именем или же нет. Т.к. если уже существует, то попытка создать лист с таким же именем неизбежно приведет к ошибке. Можно, конечно, поставить обработчик ошибки On Error.

Sub Add_New_Sheet()
    On Error Resume Next
    Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
End Sub
Sub Add_New_Sheet()
    On Error Resume Next
    Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
End Sub

Но тогда, если лист с таким именем уже существует, будет создан лист со следующим порядковым номером(типа Лист4). А этого в большинстве случаев не надо, т.к. обычно планируется все же либо создать лист с нужным именем, либо не создавать вовсе.
Я обычно проверяю так:

Sub Add_New_Sheet()
    Dim wsSh As Worksheet
    On Error Resume Next
    Set wsSh = Sheets("Новый лист")
    If wsSh Is Nothing Then Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
    'здесь можно либо активировать лист, либо производить еще какие действия
    'wsSh.Activate
End Sub
Sub Add_New_Sheet()
    Dim wsSh As Worksheet
    On Error Resume Next
    Set wsSh = Sheets("Новый лист")
    If wsSh Is Nothing Then Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
    'здесь можно либо активировать лист, либо производить еще какие действия
    'wsSh.Activate
End Sub

Если предполагается использовать такую проверку более одного раза в коде, то имеет смысл вынести проверку в отдельную функцию

Function Sh_Exist(sName As String) As Boolean
    Dim wsSh As Worksheet
    On Error Resume Next
    Set wsSh = Sheets(sName)
    Sh_Exist = Not wsSh Is Nothing
End Function
Function Sh_Exist(sName As String) As Boolean
    Dim wsSh As Worksheet
    On Error Resume Next
    Set wsSh = Sheets(sName)
    Sh_Exist = Not wsSh Is Nothing
End Function

Тогда достаточно будет одной строки кода, в которой будет меняться только имя листа:

Sub Add_New_Sheet()
    If Not Sh_Exist("Новый лист") Then
        Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
    End If
End Sub
Sub Add_New_Sheet()
    If Not Sh_Exist("Новый лист") Then
        Sheets.Add(, Sheets(Sheets.Count)).Name = "Новый лист"
    End If
End Sub

Также см.:
Как проверить открыта ли книга?

Как отправить письмо из Excel?

 

Наверное, многие уже сталкивались с кодами отправки писем из Excel. Как правило это делается через Outlook. В данной статье я предлагаю способ без использования данной почтовой программы. Итак, код:

Option Explicit
 
Sub Send_Mail()
    Const CDO_Cnf = "http://schemas.microsoft.com/cdo/configuration/"
    Dim oCDOCnf As Object, oCDOMsg As Object
    Dim SMTPserver As String, sUsername As String, sPass As String, sMsg As String
    Dim sTo As String, sFrom As String, sSubject As String, sBody As String, sAttachment As String
    On Error Resume Next
    'sFrom – как правило совпадает с sUsername
    SMTPserver = "smtp.yandex.ru"    ' SMTPServer: для Mail.ru "smtp.mail.ru"; для Яндекса "smtp.yandex.ru"; для Рамблера "mail.rambler.ru"
    sUsername = "YourMail@mail.ru"    ' Учетная запись на сервере
    sPass = "1234"    ' Пароль к почтовому аккаунту

    If Len(SMTPserver) = 0 Then MsgBox "Не указан SMTP сервер", vbInformation, "www.Excel-VBA.ru": Exit Sub
    If Len(sUsername) = 0 Then MsgBox "Не указана учетная запись", vbInformation, "www.Excel-VBA.ru": Exit Sub
    If Len(sPass) = 0 Then MsgBox "Не указан пароль", vbInformation, "www.Excel-VBA.ru": Exit Sub
 
    sTo = "AddressTo@mail.ru"    'Кому
    sFrom = "YourMail@yandex.ru"    'От кого
    sSubject = "Автоотправка"    'Тема письма
    sBody = "Привет от Excel-VBA"    'Текст письма
    sAttachment = "C:/Temp/Книга1.xls"    'Вложение(полный путь к файлу)
    'Проверка наличия файла по указанному пути
    If Dir(sAttachment, vbDirectory) = "" Then sAttachment = ""
    'Назначаем конфигурацию CDO
    Set oCDOCnf = CreateObject("CDO.Configuration")
    With oCDOCnf.Fields
        .Item(CDO_Cnf & "sendusing") = 2
        .Item(CDO_Cnf & "smtpauthenticate") = 1
        .Item(CDO_Cnf & "smtpserver") = SMTPserver
        .Item(CDO_Cnf & "sendusername") = sUsername
        .Item(CDO_Cnf & "sendpassword") = sPass
        .Update
    End With
    'Создаем сообщение
    Set oCDOMsg = CreateObject("CDO.Message")
    With oCDOMsg
        Set .Configuration = oCDOCnf
        .BodyPart.Charset = "koi8-r"
        .From = sFrom
        .To = sTo
        .Subject = sSubject
        .TextBody = sBody
        If Len(sAttachment) > 0 Then .AddAttachment sAttachment
        .Send
    End With
 
    Select Case Err.Number
    Case -2147220973: sMsg = "Нет доступа к Интернет"
    Case -2147220975: sMsg = "Отказ сервера SMTP"
    Case 0: sMsg = "Письмо отправлено"
    End Select
    MsgBox sMsg, vbInformation, "www.Excel-VBA.ru"
    Set oCDOMsg = Nothing: Set oCDOCnf = Nothing
End Sub
Option Explicit

Sub Send_Mail()
    Const CDO_Cnf = "http://schemas.microsoft.com/cdo/configuration/"
    Dim oCDOCnf As Object, oCDOMsg As Object
    Dim SMTPserver As String, sUsername As String, sPass As String, sMsg As String
    Dim sTo As String, sFrom As String, sSubject As String, sBody As String, sAttachment As String
    On Error Resume Next
    'sFrom – как правило совпадает с sUsername
    SMTPserver = "smtp.yandex.ru"    ' SMTPServer: для Mail.ru "smtp.mail.ru"; для Яндекса "smtp.yandex.ru"; для Рамблера "mail.rambler.ru"
    sUsername = "YourMail@mail.ru"    ' Учетная запись на сервере
    sPass = "1234"    ' Пароль к почтовому аккаунту

    If Len(SMTPserver) = 0 Then MsgBox "Не указан SMTP сервер", vbInformation, "www.Excel-VBA.ru": Exit Sub
    If Len(sUsername) = 0 Then MsgBox "Не указана учетная запись", vbInformation, "www.Excel-VBA.ru": Exit Sub
    If Len(sPass) = 0 Then MsgBox "Не указан пароль", vbInformation, "www.Excel-VBA.ru": Exit Sub

    sTo = "AddressTo@mail.ru"    'Кому
    sFrom = "YourMail@yandex.ru"    'От кого
    sSubject = "Автоотправка"    'Тема письма
    sBody = "Привет от Excel-VBA"    'Текст письма
    sAttachment = "C:/Temp/Книга1.xls"    'Вложение(полный путь к файлу)
    'Проверка наличия файла по указанному пути
    If Dir(sAttachment, vbDirectory) = "" Then sAttachment = ""
    'Назначаем конфигурацию CDO
    Set oCDOCnf = CreateObject("CDO.Configuration")
    With oCDOCnf.Fields
        .Item(CDO_Cnf & "sendusing") = 2
        .Item(CDO_Cnf & "smtpauthenticate") = 1
        .Item(CDO_Cnf & "smtpserver") = SMTPserver
        .Item(CDO_Cnf & "sendusername") = sUsername
        .Item(CDO_Cnf & "sendpassword") = sPass
        .Update
    End With
    'Создаем сообщение
    Set oCDOMsg = CreateObject("CDO.Message")
    With oCDOMsg
        Set .Configuration = oCDOCnf
        .BodyPart.Charset = "koi8-r"
        .From = sFrom
        .To = sTo
        .Subject = sSubject
        .TextBody = sBody
        If Len(sAttachment) > 0 Then .AddAttachment sAttachment
        .Send
    End With

    Select Case Err.Number
    Case -2147220973: sMsg = "Нет доступа к Интернет"
    Case -2147220975: sMsg = "Отказ сервера SMTP"
    Case 0: sMsg = "Письмо отправлено"
    End Select
    MsgBox sMsg, vbInformation, "www.Excel-VBA.ru"
    Set oCDOMsg = Nothing: Set oCDOCnf = Nothing
End Sub

Данный код отправляет письмо, используя объект CDO(Collaboration Data Objects) и от имени Вашей учетной записи(либо Яндекс, либо Мэйл, либо Рамблер либо др.).

SMTPserver — Каждый из приведенных выше сервисов имеет свой сервер для отправки сообщений(его можно посмотреть на сайте сервиса). В комментариях к коду я написал три самых распространенных, но если Вы используете какой-то другой, то просто посмотрите на его сайте настройки для Outlook и отыщите тот параметр, который отвечает за SMTPserver.

sUsername — это Ваш логин для входа в почтовый сервис. Думаю тут все понятно. Единственный момент — обязательно указывать e-mail именно в полном виде — YourMail@mail.ru, даже если для входа на сервис через браузер Вы используете только первую часть записи(YourMail).

sPass — пароль доступа к Вашей учетной записи, который Вы используете для входа в почту.

Это основные моменты. Поля Кому(sTo), От кого(sFrom),Тема письма(sSubject), Текст письма(sBody) и Вложение(sAttachment) думаю не нуждаются в расшифровке.

Чтобы использовать данный код вы можете либо просто скопировать его прямо со страницы, либо скачать файл. В файле программа немного упрощена к использованию — в ячейки листа вам необходимо будет внести поля: Кому(sTo), От кого(sFrom),Тема письма(sSubject), Текст письма(sBody) и Вложение(sAttachment) и выбрать SMTPserver. SMTPserver выбирается из выпадающего списка. Сам список является динамическим и расположен на листе «Settinngs«. Там же расположены поля Учетной записи и Пароль, которые автоматически подставляются в необходимые поля на листе «Отправка«. Т.к. список динамический Вы можете просто добавлять к уже имеющимся новые сервисы и потом просто выбирать их из списка. Так же в файле есть еще одна возможность — выбрать файл. Для этого надо просто нажать на кнопку и выбрать файл.

Скачать пример »

  Tips_Macro_SendMailCDO.xls (51,5 KiB, 1 618 скачиваний)

Также см.:
Отправка листа/книги по почте

Categories: Tags:

Как сохранить картинки из листа Excel в картинки JPG

 

Вы получили по почте файл-прайс с изображениями товара и эти картинки нужны Вам? Но не на листе Excel, а именно как картинки? Или Вы просто хотите картинки из листа Excel сохранить как обычные, но…Как? Ведь даже команды в Excel такой нет. Вставить картинки можно, а вот обратно — увы…А если таких книг много? И из всех надо сохранить картинки? Вот я и подумал — надо бы написать некий код, который бы исправил данную несправедливость. Ну и, конечно, решил сделать код не только для себя, но и для всех, кому это может вдруг понадобиться. Собственно, вот и код:

Sub Save_Object_As_Picture()
    Dim avFiles, li As Long, oObj As Object, wsSh As Worksheet
    Dim sBookName As String, sName As String
 
    avFiles = Application.GetOpenFilename("Excel Files(*.xls*),*.xls*", , "Выбрать файлы", , True)
    If VarType(avFiles) = vbBoolean Then Exit Sub
 
    On Error Resume Next
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
 
    For li = LBound(avFiles) To UBound(avFiles)
        Workbooks.Open avFiles(li)
        sBookName = ActiveWorkbook.Name
        For Each wsSh In Sheets
            For Each oObj In wsSh.Shapes
                If oObj.Type = 13 Then
                    '13 – картинки
                    '1 – автофигуры
                    '3 – диаграммы
                    oObj.Copy
                    Workbooks.Add
                    ActiveWorkbook.SaveAs ThisWorkbook.Path & Application.PathSeparator & "TempBook.xls"
                    sName = ThisWorkbook.Path & Application.PathSeparator & sBookName & "_" & wsSh.Name & "_" & oObj.Name
                    ActiveSheet.Paste
                    With ActiveSheet.ChartObjects.Add(0, 0, oObj.Width, oObj.Height).Chart
                        .Paste
                        .Export Filename:=sName & ".jpg", FilterName:="JPG"
                    End With
                    ActiveWorkbook.ChangeFileAccess xlReadOnly
                    Kill ActiveWorkbook.FullName
                    ActiveWorkbook.Close 0
                End If
            Next oObj
        Next wsSh
        ActiveWorkbook.Close 0
    Next li
    Set oObj = Nothing: Set wsSh = Nothing
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    MsgBox "Объекты сохранены в папке: " & ThisWorkbook.Path, vbInformation, "www.excel-vba.ru"
End Sub
Sub Save_Object_As_Picture()
    Dim avFiles, li As Long, oObj As Object, wsSh As Worksheet
    Dim sBookName As String, sName As String

    avFiles = Application.GetOpenFilename("Excel Files(*.xls*),*.xls*", , "Выбрать файлы", , True)
    If VarType(avFiles) = vbBoolean Then Exit Sub

    On Error Resume Next
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False

    For li = LBound(avFiles) To UBound(avFiles)
        Workbooks.Open avFiles(li)
        sBookName = ActiveWorkbook.Name
        For Each wsSh In Sheets
            For Each oObj In wsSh.Shapes
                If oObj.Type = 13 Then
                    '13 – картинки
                    '1 – автофигуры
                    '3 – диаграммы
                    oObj.Copy
                    Workbooks.Add
                    ActiveWorkbook.SaveAs ThisWorkbook.Path & Application.PathSeparator & "TempBook.xls"
                    sName = ThisWorkbook.Path & Application.PathSeparator & sBookName & "_" & wsSh.Name & "_" & oObj.Name
                    ActiveSheet.Paste
                    With ActiveSheet.ChartObjects.Add(0, 0, oObj.Width, oObj.Height).Chart
                        .Paste
                        .Export Filename:=sName & ".jpg", FilterName:="JPG"
                    End With
                    ActiveWorkbook.ChangeFileAccess xlReadOnly
                    Kill ActiveWorkbook.FullName
                    ActiveWorkbook.Close 0
                End If
            Next oObj
        Next wsSh
        ActiveWorkbook.Close 0
    Next li
    Set oObj = Nothing: Set wsSh = Nothing
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    MsgBox "Объекты сохранены в папке: " & ThisWorkbook.Path, vbInformation, "www.excel-vba.ru"
End Sub

Данный код позволяет выбрать одну или несколько книг Excel и сохраняет все картинки со всех листов выбранных книг в папку с книгой, в которой расположен данный код. Если Вам надо сохранить не только картинки, но и другие объекты, то необходимо в коде в этой строке: If oObj.Type = 13 Then изменить цифру. В коде комментариями указано, какая цифра какой тип обозначает.

Это может понадобится не всем и весьма нечасто, но если уж понадобится, так понадобится.

Скачать пример »

  Tips_Macro_Save_Object_As_Picture.xls (46,5 KiB, 1 379 скачиваний)

Также см.:
Сохранить диаграммы в графический файл
Сохранение выделенного диапазона в графический файл
Что такое модуль? Какие бывают модули?

Categories: Tags:

Как скопировать картинку из примечания?

 

Сделать такую штутку мне пришло в голову случайно, когда это понадобилось кому-то на одном из форумов, на которых я общаюсь. Вот и решил — если это нужно кому-то одному, то может понадобится и другим. Изначально макрос копирования картинки из примечания появился в надстройке MyAddin — Копирование картинки из примечания. Теперь решил поделиться кодом в этой статье, т.к. возможно это поможет кому-то в решении подобной проблемы. Что делает код ниже? Он копирует каритнки из примечаний в выделенных ячейках. Т.е. сначала Вы выделяете одну или несколько ячеек, а затем запускаете код. Вот и все. После этого в ячейке, расположенной правее ячейки с примечанияем будет картинка этого примечания.

Sub Copy_Picture_From_comment()
    If TypeName(Selection) <> "Range" Then MsgBox "Выделенная область не является диапазоном!", vbCritical, "Ошибка": Exit Sub
    Dim rRange As Range, rCell As Range, oComment As Comment
    Dim bVisible As Boolean
 
    On Error Resume Next
    Set rRange = Selection.SpecialCells(xlCellTypeComments)
    If rRange Is Nothing Then Exit Sub
    Application.ScreenUpdating = 0
 
    For Each rCell In rRange
        Set oComment = rCell.Comment
        If Not oComment Is Nothing Then
            bVisible = oComment.Visible
            With rCell
                .Comment.Visible = True
                .Comment.Shape.CopyPicture xlScreen, xlBitmap
                .Offset(, 1).PasteSpecial
                .Comment.Visible = bVisible
            End With
        End If
    Next rCell
    Application.ScreenUpdating = 1
    Set rRange = Nothing: Set rCell = Nothing: Set oComment = Nothing
 
End Sub
Sub Copy_Picture_From_comment()
    If TypeName(Selection) <> "Range" Then MsgBox "Выделенная область не является диапазоном!", vbCritical, "Ошибка": Exit Sub
    Dim rRange As Range, rCell As Range, oComment As Comment
    Dim bVisible As Boolean

    On Error Resume Next
    Set rRange = Selection.SpecialCells(xlCellTypeComments)
    If rRange Is Nothing Then Exit Sub
    Application.ScreenUpdating = 0

    For Each rCell In rRange
        Set oComment = rCell.Comment
        If Not oComment Is Nothing Then
            bVisible = oComment.Visible
            With rCell
                .Comment.Visible = True
                .Comment.Shape.CopyPicture xlScreen, xlBitmap
                .Offset(, 1).PasteSpecial
                .Comment.Visible = bVisible
            End With
        End If
    Next rCell
    Application.ScreenUpdating = 1
    Set rRange = Nothing: Set rCell = Nothing: Set oComment = Nothing

End Sub

Если вдруг Вам данные картинки надо сохранить не на лист Excel, а на диск, то Вы можете сначала применить код выше, а затем код, выложенный в этой статье для сохранения на диск. Ну а тем, кто хоть немного разбирается в кодах, не составит труда при необходимости совместить коды из этих двух статей в один единый.

Скачать пример »

  Tips_Macro_Copy_Picture_from_Comments.xls (52,0 KiB, 609 скачиваний)

Также см.:
Копирование картинки из примечания
Как сохранить картинки из листа Excel в картинки JPG

Categories: Tags:

Excel удаляет вместо отфильтрованных строк — все?! Как избежать

 

С появлением новой версии Excel — 2007 появилась возможность работать с данными более 1000000 строк(если точно — 1048576), строить большие сводные таблицы, фильтровать и сортировать данные по цвету и т.д. Но появились и неудобства. Об одном таком неудобстве и пойдет речь в этой статье. При активном фильтре на листе в 2003 Excel можно было просто выделить отфильтрованные строки и удалить. В 2007 если сделать также, то удаляются не только отфильтрованные строки, но и все, что между ними! Да и все операции с отфильтрованными данными применяются почему-то не к отфильтрованным данным, а ко всем. Поначалу это пугает и вкрадывается подозрение, что новую версию Excel разрабатывали диверсанты с одной целью — не дать Вам нормально работать. Но нет. Этот «глюк» легко обойти. Есть пара способов.

Способ первый
Необходимо просто вынести на Панель быстрого доступа команду — «Выделить видимые ячейки«. Делается это следующим образом: Меню-Параметры Excel-Настройка-Все команды-Выделить видимые ячейки.

Добавление команды на панель

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

Второй способ
Перед удалением строк(или другим действием) выделяете необходимые ячейки, жмете Alt+ж(для русской раскладки и соответственно Alt+; для английской). Это быстрые клавиши для выделения видимых ячеек. А далее? Правильно — применяете действие(удаление, вставка формулы и т.д.). Данное действие будет применено именно к видимым отфильтрованным ячейкам.

Третий способ

Хочется добавить, что такая проблема в основном наблюдается у пользователей, у которых установлены не все обновления для Microsoft Office. Но может наблюдаться и у других. Проблему можно попытаться устранить с помощью установки пакета обновлений SP2 для Microsofs Office, который можно скачать с офф.сайта.

Categories: Tags: