1

My main goal is to be able to autofilter merged cells in one column.
In the picture below I want row 7-9 to disappear when I remove "6" from the autofilter menu. example
But as I have figured, I need the value "6" to be held in all the cells "L7:L9" in order for Excel to do so.

The number 6 is calculated by adding "Num1" and "Num2" (2 * 3) by the following function I have placed in "L7":

Function Exposure(arg1 As Range, arg2 As Range) As Variant
Application.EnableEvents = False
Application.Calculation = xlManual

If Application.ThisCell.Offset(, -1).Value <> "-" And Application.ThisCell.Offset(, -2).Value <> "-" Then
       Exposure = Left(Application.ThisCell.Offset(, -1).Value, 1) * Left(Application.ThisCell.Offset(, -2).Value, 1)
End If
If Exposure = 0 Then Exposure = "-"

Application.Calculation = xlAutomatic
Application.EnableEvents = True
End Function

I put the following formula inside the merged cell "L7":=Exposure(K7;J7). Then formula is dragged down.
"Num1" and "Num2" are controlled by valdiation fields, drop-down menu.

My plan was to unmerge after calculating the Exposure Variant, fill the same value in the remaining rows, then re-merge the same area. So I wrote this stand alone Sub:

Sub WorkingSub(rng As Range)
'Set rng = ActiveCell.MergeArea
rng.UnMerge
For i = 2 To rng.Cells.Count
    rng.Cells(i).Value = rng.Cells(1).Value 'This line triggers recursion
Next i
rng.Offset(rng.Cells.Count).Copy 'Copies format from below
rng.PasteSpecial Paste:=xlPasteFormats 'Paste that keeps the values even after merging
End Sub

Which works on its own, but not when called inside the function above. After setting the first value, the function triggers "something", debug show the the function starting over, skipping the rng.PasteSpecial Paste:=xlPasteFormats code.

So my question to you guys is how do i write my function(s) to stop "recursing" and let me unmerge during the function call?

Or am I attacking this the wrong way? What would you do?

I am stuck with merged cells for lots of reasons, this is just one part of many inside this spreadsheet.

Doons
  • 183
  • 11
  • 1
    "What would you do?". Not use merged cells. Sorry but you did ask. – SJR Mar 19 '21 at 13:12
  • Me too...But the main concept of the sheet, or to be even more concise, my boss, tells me "This is the way" – Doons Mar 19 '21 at 13:19
  • Could you not just hide the row above and below the 6? – SJR Mar 19 '21 at 13:24
  • On a permanent basis, no. There are non-merged rows in other columns. But if it's possible to catch the event where the user presses OK in the autofilter (built in filter in Excel), then write code to hide un-hide in order to end up removing the blank rows in the merged cell...? – Doons Mar 19 '21 at 13:37
  • 1
    https://stackoverflow.com/questions/28979396/excel-vba-filter-change-event-handler – SJR Mar 19 '21 at 13:44
  • One possibility is to "catch" the filter as it's applied to the data range, then "apply" that filter with the code while retaining the merged cell structure that exists. All of that is possible, though you're performing all of the filtering in your code rather than letting Excel do the work. One issue I see with this approach, however, is that the "filter" drop-down item would not accurately reflect what is really filtered. So you'd go down a rabbit-hole of creating your own drop-down filter control. None of this is majorly hard, but it's all quite involved if you want to go there. – PeterT Mar 19 '21 at 14:09
  • @SJR that's exactly the solution I was starting to think about. Along with [this](https://stackoverflow.com/a/9490281/4717755) – PeterT Mar 19 '21 at 14:10
  • @Toddleson, you might be right. But if I comment out that code, Excel still wont fill values in the now unmerged rows while executed from my function. A cell change is detected and somehow starts a re-calculation, even with application.calculation turned off. – Doons Mar 19 '21 at 14:14
  • hmmm. @PeterT and SJR. Seems to plausible indeed! But it might be a speed degrading solution to unmerge and merge all rows in the sheet in one go,before applying the filter, rather than just one merged cell on each calculation? It might be 250 rows with at least 80 merge and umerges in one go? – Doons Mar 19 '21 at 14:28
  • Personally, I wouldn't unmerge-merge the cells. I would selectively hide the rows (with the merged cells) that don't match the filter criteria. Effectively, you're writing your own filtering function that operates on merged rows. (If you can guarantee that EVERY "ROW" in your data has "cells" consisting of three merged rows?) I wouldn't be too concerned with speed since you can disable/enable screen updating and calculations during the operation. – PeterT Mar 19 '21 at 15:51
  • @PeterT I thought I'd comment that no, every element in the #-column has at least 3, but no upper limit on the number of merged rows. Does that make un-merging and merging more suitable? I reckon you'd loop firstRow->lastRow and used mergedArea to hide all the cells, no matter the number of merged rows? – Doons Mar 20 '21 at 12:45

2 Answers2

1

An interesting problem. You can capture the filter event through trapping a change in a calculation and then processing the rows of the table for visibility. I've made some assumptions for the initial table range assignment which may need some alteration.

The If Not VisRange Is Nothing Then is actually redundant as the prior line will throw a fit if an empty range is assigned, but I just kept it in. In order to get around having a null range, keep the header range in the initial MergedTableRange so there will always be a row visible

Within a cell either somewhere in the same worksheet or a 'dummy' worksheet

=SUBTOTAL(103,Sheet1!A3:H10) 'Or other table range

In the worksheet module code

Private Sub Worksheet_Calculate()
    Dim ws As Worksheet: Set ws = Worksheets("Sheet1")
    Dim MergedTableRange As Range: Set MergedTableRange = ws.Range("A2").CurrentRegion
    
    Dim Cell As Range
    Dim VisRange As Range: Set VisRange = MergedTableRange.SpecialCells(xlCellTypeVisible)
    If Not VisRange Is Nothing Then
        For Each Cell In VisRange
            If Not Application.Intersect(Cell.MergeArea, VisRange).Address = Cell.MergeArea.Address Then
                Cell.Rows.Hidden = True
            End If
        Next Cell
    End If
End Sub
Tragamor
  • 3,594
  • 3
  • 15
  • 32
  • I tested this answer right now. This might actually be very good! :D Any idea on how to catch what column was filtered? It's a bit slow when hiding " -" entries on Range"C6:Y86". I've seen people with more than 300 rows. So checking the correct column instead of the whole table would be perfect – Doons Mar 20 '21 at 19:39
  • Hmmm. I actually think this `With Sht.AutoFilter For i = 1 To .Filters.Count If .Filters(i).On Then MsgBox .Range(1, i).Column End If Next i End With` did it – Doons Mar 20 '21 at 19:57
0

I came up with a different approach. Maybe there's a downside I'm missing. But my few test runs have succeeded.

I allready have a hidden sheet named "Template" where the formats for each new "#" is stored. So whenever the user wants to insert a new row, the template have the merged and the non-merged cells ready and insert is done through copy paste.

In that same sheet I made 2 merged rows in column 2, 3 merged cells in column 3 and so on:merged rows

This way I'm able to copy the correct number of merged rows to paste after filling the unmerged rows with their correct values.

I came to the conclusion that I could catch a Worksheet_change on the "Num1" and "Num2" columns instead of catching and canceling an autofilter call.

So I added:

Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("J:J")) Is Nothing Then
        Call UnMergeMerge(Cells(Target.Row, "L").MergeArea)
End If
If Not Intersect(Target, Target.Worksheet.Range("K:K")) Is Nothing Then
        Call UnMergeMerge(Cells(Target.Row, "L").MergeArea)
End If
End Sub

And the UnMergeMerge sub ended up being:

Sub UnMergeMerge(rng As Range)
Application.EnableEvents = False
Application.ScreenUpdating = False
rng.UnMerge
For i = 2 To rng.Cells.Count
    rng.Cells(i).Value = rng.Cells(1).Value
Next i
With Sheets("Template")
    .Range(.Cells(8, rng.Cells.Count), .Cells(8 + rng.Cells.Count, rng.Cells.Count)).Copy
End With
rng.PasteSpecial Paste:=xlPasteFormats
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

Still not sure it's the fastest and best approach...Do you guys still believe catching, undoing and running a different autofilter would be more effective?

Doons
  • 183
  • 11