VBA для подключения слайсеров (ищем улучшения в коде)

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

Я добавил .Application.ScreenUpdating и .Application.EnableEvents в попытке ускорить выполнение макроса, но он все еще тормозит и перестает отвечать на запросы.

Есть ли более прямой способ кодирования этого или есть ли здесь какие-либо потенциально нестабильные строки, заставляющие Excel сжигать мозг?

Private Sub Worksheet_PivotTableUpdate _
    (ByVal Target As PivotTable)
Dim wb As Workbook
Dim scShort As SlicerCache
Dim scLong As SlicerCache
Dim siShort As SlicerItem
Dim siLong As SlicerItem

Application.ScreenUpdating = False
Application.EnableEvents = False
On Error GoTo errHandler
Application.ScreenUpdating = False
Application.EnableEvents = False

Set wb = ThisWorkbook
Set scShort = wb.SlicerCaches("Slicer_Department")
Set scLong = wb.SlicerCaches("Slicer_Department2")

scLong.ClearManualFilter

For Each siLong In scLong.VisibleSlicerItems
    Set siLong = scLong.SlicerItems(siLong.Name)
    Set siShort = Nothing
    On Error Resume Next
    Set siShort = scShort.SlicerItems(siLong.Name)
    On Error GoTo errHandler
    If Not siShort Is Nothing Then
        If siShort.Selected = True Then
            siLong.Selected = True
        ElseIf siShort.Selected = False Then
            siLong.Selected = False
        End If
    Else
        siLong.Selected = False
    End If
Next siLong

exitHandler:
    Application.ScreenUpdating = True
    Application.EnableEvents = True
    Exit Sub

errHandler:
    MsgBox "Could not update pivot table"
    Resume exitHandler
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub

исходный код найден в Contexture

Спасибо за любой совет, как всегда.

ссылка на исходный запрос:


person Awill    schedule 20.09.2016    source источник
comment
Сколько элементов слайсера вы перебираете?   -  person Kyle    schedule 20.09.2016
comment
Вероятно, это будет просто медленно. Обеззараживание с элементами срезов в кэше срезов вызывает фильтрацию в подключенном к нему сводном кэше, что требует обработки. Таким образом, каждый раз, когда он меняет sliceritem.selected на true или false, сводный кеш фильтруется для связанной сводной таблицы и выполняет обходы в Excel. Я думаю ... теоретически вы могли бы очистить сводную кэш подключенной сводной таблицы (временно переместить данные, но не заголовки и обновить), затем запустить этот код, чтобы отфильтровать ничего, переключив свойство sliceritems.Selected, а затем перебросив данные обратно и обновив сводную все сразу...?   -  person JNevill    schedule 20.09.2016
comment
@ Кайл Алот и, вероятно, еще не все. Мне интересно, было бы лучшим / более быстрым решением установить значение / выбор слайсера2 в соответствии со скрытой ячейкой? например иметь A1 = отфильтрованное значение основной сводной таблицы, а затем установить выбор среза 2 равным значению ячейки A1? Я не уверен, как это расшифровать, пока не нашел никакого функционального кодирования.   -  person Awill    schedule 20.09.2016
comment
Сколько элементов будет выбрано в каждом слайсере? Только 1? Или вы хотите, чтобы пользователи могли делать множественный выбор?   -  person jeffreyweir    schedule 21.09.2016
comment
И когда вы говорите «много и, вероятно, еще впереди», можете ли вы быть более конкретными? Сколько сводных таблиц вам нужно синхронизировать? Сколько PivotItems в каждом из них?   -  person jeffreyweir    schedule 21.09.2016
comment
У меня есть одна основная сводная таблица и диаграмма из одного кеша, а затем 4 дополнительных сводных таблицы из другого кеша. Я хочу, чтобы был выбран только один элемент для каждого из двух желаемых слайсеров. У одного из слайсеров будет 150+ предметов, у другого - всего несколько месяцев. Я попробовал немного поработать с синхронизацией сводных точек, а не слайсера, но это не сработает для меня, я не думаю, так как у меня нет поля соответствия для того, что я фильтрую с помощью слайсера. Например, дополнительные сводные таблицы имеют только поля, пользователи с количеством пользователей для отображения первых 5 ....   -  person Awill    schedule 21.09.2016
comment
... пользователей по заданным критериям. У меня нет поля, соответствующего слайсеру с более чем 150 элементами или датой поля, и я не хочу их добавлять. Однако, когда я выбираю отдел из среза (срез с более чем 150 элементами) или месяц со второго среза, он соответствующим образом фильтрует синхронизированные таблицы. Мне не удалось добиться этого напрямую с помощью сводных таблиц, потому что отсутствует поле страницы. Стоит также отметить, что я невероятно неопытен с VBA и только недавно начал баловаться, поэтому я не уверен, будет ли использование ведомых опорных точек обходным путем для этого.   -  person Awill    schedule 21.09.2016
comment
Мой подход Подключиться к слайсерам, синхронизировать подчиненные устройства с VBA будет работать, даже если в ваших отображаемых сводных точках нет поля «Отдел». См. Новый снимок экрана в моем ответе ниже. Попробуйте и кричите, если вы застряли. В противном случае вы застрянете на медленном итерационном подходе, который у вас есть.   -  person jeffreyweir    schedule 22.09.2016
comment
Получил работать в тестовом файле. Это потрясающе, спасибо !. Однако я изо всех сил пытаюсь понять, как заставить это работать со вторым полем среза. Я хочу, чтобы пользователь мог выбрать только один элемент в слайсере, но мне нужен дополнительный для дат. Я попытался создать второй набор ведомых устройств и повторить код, но получил повторяющуюся ошибку sField. Это возможно?   -  person Awill    schedule 22.09.2016


Ответы (2)


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

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

    введите описание изображения здесь

  2. Убедитесь, что флажок "Выбрать несколько элементов" не установлен для каждой из подчиненных сводных таблиц:  введите описание изображения здесь
  3. Добавьте слайсер к каждому из этих ведомых устройств. Опять же, они будут где-то вне поля зрения:  введите описание изображения здесь
  4. Подключите каждый из этих срезов к фактическим сводным таблицам, с которых вы должны были начать. (т. е. подключите каждый скрытый срез к его видимой сводной таблице с помощью поля «Соединения отчетов».  введите описание изображения здесь

Вот тут-то и пригодится умный прием: мы перемещаем слайсер, подключенный к подчиненной таблице PivotTable1 Slave, на основной лист, чтобы пользователь мог щелкнуть по нему. Когда они выбирают элемент, использующий его, он генерирует событие PivotTable_Update для этой подчиненной сводной таблицы PivotTable1, за которой мы следим. А затем мы устанавливаем .PageField этих других подчиненных сводных таблиц в соответствие с .PageField подчиненной таблицы PivotTable1 Slave. И затем происходит еще одна магия: этот единственный выбор в этих подчиненных полях страницы реплицируется в основных сводных таблицах благодаря тем скрытым слайсерам, которые мы настроили ранее. Нет необходимости в VBA. Нет необходимости в медленной итерации. Просто молниеносная синхронизация.

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

... и это будет работать, даже если поле, которое вы хотите фильтровать, не отображается ни в одной из ваших сводок:  введите описание изображения здесь

Вот код, который этого добивается:

Option Explicit

Private Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)

Dim pt As PivotTable
Dim pf As PivotField
Dim sCurrentPage As String
Dim vItem As Variant
Dim vArray As Variant

'########################
'# Change these to suit #
'########################

Const sField As String = "Name"
vArray = Array("PivotTable2 Slave", "PivotTable3 Slave")


If Target.Name = "PivotTable1 Slave" Then
    On Error GoTo errhandler
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
        .EnableEvents = False
    End With

    'Find out what item they just selected
    Set pf = Target.PivotFields(sField)
    With pf
        If .EnableMultiplePageItems Then
            .ClearAllFilters
            .EnableMultiplePageItems = False
            sCurrentPage = "(All)"
        Else:
            sCurrentPage = .CurrentPage
        End If
    End With

    'Change the other slave pivots to match. Slicers will pass on those settings
    For Each vItem In vArray
        Set pt = ActiveSheet.PivotTables(vItem)
        Set pf = pt.PivotFields(sField)
        With pf
            If .CurrentPage <> sCurrentPage Then
                .ClearAllFilters
                .CurrentPage = sCurrentPage
            End If
        End With
    Next vItem

errhandler:
    With Application
        .ScreenUpdating = True
        .Calculation = xlCalculationAutomatic
        .EnableEvents = True
    End With
End If

End Sub

Там есть немного кода, чтобы гарантировать, что пользователь не может выбрать более одного элемента в срезе за раз.

Но что, если вы хотите, чтобы пользователь мог выбирать несколько элементов?

Если вы хотите, чтобы пользователь мог выбирать несколько элементов, все становится намного сложнее. Для начала вам необходимо установить для свойства ManualUpdate каждой сводной таблицы значение TRUE, чтобы они не обновлялись при каждом изменении PivotItems. И даже тогда на синхронизацию только одной сводной таблицы, если в ней, скажем, 20 000 элементов, могут уйти минуты. У меня есть хороший пост по следующей ссылке, который я рекомендую вам прочитать, который показывает, сколько времени требуется для выполнения различных действий, когда дело доходит до итерации большого количества PivotItems: http://dailydoseofexcel.com/archives/2013/11/14/filtering-pivots-based-on-external-ranges/

Даже тогда вам придется преодолеть множество других проблем в зависимости от того, что вы делаете. Во-первых, слайсеры действительно замедляют работу. Прочтите мой пост на странице http://dailydoseofexcel.com/archives/2015/11/17/filtering-pivottables-with-vba-deselect-slicers-first/, чтобы узнать больше об этом.

Я нахожусь на завершающей стадии запуска коммерческого дополнения, которое делает многие из этих вещей молниеносно, но до запуска останется как минимум месяц.

person jeffreyweir    schedule 20.09.2016
comment
Привет, большое спасибо за ответ, я добавил дополнительный ответ к вашим комментариям над ответом. Я не уверен, сработает ли это для меня, как вы прочитаете выше. Другой подход, который я пытался выяснить, заключался в том, могу ли я отправить выбранный элемент слайсера 1 в ячейку и использовать это значение для управления выбором слайсера для таблиц во втором кэше. благодаря тонну. - person Awill; 21.09.2016
comment
Да, вы можете отправить выбранный элемент Slicer1 в ячейку, но тогда вы все равно столкнетесь с проблемой синхронизации Slicer2 с этим одним элементом. Что потребует от вас повторения SlicerItems в Slicer2 (или одной из сводных таблиц, к которым он подключается), и это вернет вас обратно к вашей первоначальной проблеме. Прочтите эту ссылку, потому что она объясняет связанные с этим проблемы: dailydoseofexcel.com/archives/2013/11/14/ - person jeffreyweir; 22.09.2016
comment
Если вы установите для .ManualUpdate значение true во всех опорных точках, подключенных к Slicer2, пока вы это делаете, тогда все должно стать быстрее. Но это все еще чертовски сложнее, чем предложенный мной выше подход с использованием ведомых поворотных точек, чтобы «собрать» щелчок на Slicer1, а затем эффективно синхронизировать этот единственный выбор с другими поворотными точками через Slicer2. - person jeffreyweir; 22.09.2016
comment
Я не могу понять, как это можно настроить, чтобы разрешить синхронизацию двух разных категорий слайсеров. Пробовал второй набор подчиненных таблиц, поле второй страницы, хм. - person Awill; 22.09.2016
comment
Хорошо ... каковы два разных названия категории слайсеров? По сути, вам нужно продублировать все, что находится между моим комментарием вверху и обработчиком ошибок внизу, а затем изменить следующие биты, чтобы отразить имя второго среза и точек поворота: Const sField As String = Name vArray = Array (PivotTable2 Slave, PivotTable3 Slave) Если Target.Name = PivotTable1 Slave, то ... - person jeffreyweir; 22.09.2016
comment
Привет, это была моя первоначальная попытка, однако я был отмечен для дублирования объявления в области видимости с выделением sField As String = all, когда я выбрал отладку. - person Awill; 22.09.2016
comment
извините пропустил это; первая категория слайсеров - это отдел. Я успешно обновляю все свои таблицы. Это дает мне общие / итоговые показатели для выбранного отдела, но я также хочу иметь возможность разбить их по дате с помощью слайсера по месяцам. - person Awill; 22.09.2016
comment
Ах да, моя беда. Измените Const sField As String = SomeFieldName на sField = SomeFieldName, а также добавьте DIM sField as String к объявлениям вверху. - person jeffreyweir; 23.09.2016
comment
Тем не менее, может быть лучший способ справиться с синхронизацией дат. См. stackoverflow.com/questions/39105890/ Кроме того, какую версию Excel вы используете? Мне кажется, что если у вас 2013 год или более поздняя версия, вам следует рассмотреть возможность использования DataModel для связывания базовых исходных таблиц, чтобы все затем могло появиться в том же PivotCache. - person jeffreyweir; 23.09.2016
comment
Подбросил свой результат наверх, так как он был слишком длинным. - person Awill; 23.09.2016
comment
Вы бы возражали, если бы я отправил вам по электронной почте свой пример вместо того, чтобы тянуть комментарии? Я также борюсь с тем, чтобы одна из моих сводных диаграмм с объединенными данными из двух других сводных данных обновлялась автоматически. Он отлично работает при обновлении вручную, но когда я пытаюсь добавить код на рабочий лист, он выходит из строя, надеюсь, я не просто нарушил какие-либо правила публикации, комментируя второй контрольно-пропускной пункт, с которым я сталкиваюсь с этим листом. - person Awill; 23.09.2016

Я не уверен, что делаю неправильно. Я разместил свой код ниже, у меня нет никаких ошибок, он просто не обновляет какие-либо другие срезы / поля. При первом тесте слайсер отдела обновил все таблицы один раз, но затем не очистил фильтр и не разрешил другой выбор, что касается слайсера «Месяц», я не заставил его работать вообще. Возможно, мне нужно продублировать каждый элемент, чтобы его можно было отдельно идентифицировать? Как в Dim sCurrentPage As String и Dim sCurrentPage2 As String. Большое вам спасибо за вашу постоянную помощь в этом, я никогда раньше не хотел, чтобы выходные прошли так плохо, когда я работаю с электронной таблицей.

Option Explicit

Private Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)

Dim pt As PivotTable
Dim pf As PivotField
Dim sCurrentPage As String
Dim vItem As Variant
Dim vArray As Variant
Dim sField As String

'########################
'# Change these to suit #
'########################

sField = "Department"
vArray = Array("PivotTable2 Slave", "PivotTable3 Slave")


If Target.Name = "PivotTable1 Slave" Then
    On Error GoTo errhandler
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
        .EnableEvents = False
    End With

    'Find out what item they just selected
    Set pf = Target.PivotFields(sField)
    With pf
        If .EnableMultiplePageItems Then
            .ClearAllFilters
            .EnableMultiplePageItems = False
            sCurrentPage = "(All)"
        Else:
            sCurrentPage = .CurrentPage
        End If
    End With

    'Change the other slave pivots to match. Slicers will pass on those settings
    For Each vItem In vArray
        Set pt = ActiveSheet.PivotTables(vItem)
        Set pf = pt.PivotFields(sField)
        With pf
            If .CurrentPage <> sCurrentPage Then
                .ClearAllFilters
                .CurrentPage = sCurrentPage
            End If
        End With
    Next vItem

'########################

sField = "Month"
vArray = Array("PivotTable2 Slave2", "PivotTable3 Slave2")


If Target.Name = "PivotTable1 Slave2" Then
    On Error GoTo errhandler
    With Application
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
        .EnableEvents = False
    End With

    'Find out what item they just selected
    Set pf = Target.PivotFields(sField)
    With pf
        If .EnableMultiplePageItems Then
            .ClearAllFilters
            .EnableMultiplePageItems = False
            sCurrentPage = "(All)"
        Else:
            sCurrentPage = .CurrentPage
        End If
    End With

    'Change the other slave pivots to match. Slicers will pass on those settings
    For Each vItem In vArray
        Set pt = ActiveSheet.PivotTables(vItem)
        Set pf = pt.PivotFields(sField)
        With pf
            If .CurrentPage <> sCurrentPage Then
                .ClearAllFilters
                .CurrentPage = sCurrentPage
            End If
        End With
    Next vItem

errhandler:
    With Application
        .ScreenUpdating = True
        .Calculation = xlCalculationAutomatic
        .EnableEvents = True
    End With
End If

End Sub
person Awill    schedule 23.09.2016
comment
Доброго времени суток - перенаправьте мне свою таблицу на [email protected], я посмотрю и отправлю ее сюда. - person jeffreyweir; 25.09.2016
comment
Учитывая, что этот ответ был направлен на ваш исходный вопрос, как насчет того, чтобы пометить его как принятый. - person jeffreyweir; 27.09.2016