Hiding columns in excel based on the value of a cell - vba

My goal is to hide the column if all value from row 3 to 10 are zero in that column, so I create formula in the row 11 which is sum of the value from row 3 to 10
Basicly I can create code like this
If Range("B11").Value = 0 Then
Columns("B:B").EntireColumn.Hidden = True
Else
Columns("B:B").EntireColumn.Hidden = False
End If
If Range("C11").Value = 0 Then
Columns("C:C").EntireColumn.Hidden = True
Else
Columns("C:C").EntireColumn.Hidden = False
End If
but how to simply this, because I want to this macro run from Column B to FV,
or maybe any other solution to achieve my goal?

A well placed loop would help and the join function:
Dim X as Long
Columns("B:FV").EntireColumn.Hidden = False
For X = 2 To 178
If Join(Application.Transpose(Range(Range(Cells(3, X).Address & ":" & Cells(10, X).Address).Address).Value), "") = "00000000" Then Columns(X).Hidden = True
Next
Unhide ALL the columns first then you have removed the need for your else statement
Edit: With this solution, you also don't need your formula in row 11.

I have surprised no one write the easiest answer.
for i = 2 to 178
if cells(11, i).value = 0 then
Columns(i).EntireColumn.Hidden = True
end if
next

Heres one way.
Sub test()
Dim iStart As Long: iStart = Range("B1").Column
Dim iFin As Long: iFin = (Range("FV1").Column) - 1
Dim iCntCol As Long: iCntCol = iStart 'Col B is #2
For iCntCol = iStart To iFin 'FV is Col # 178
If Cells(11, iCntCol).Value = 0 Then
Columns(iCntCol).EntireColumn.Hidden = True
Else
Columns(iCntCol).EntireColumn.Hidden = False
End If
Next iCntCol
End Sub
HTH

should performance be an issue, consider what follows
Option Explicit
Sub hide()
Dim found As Range
With Intersect(ActiveSheet.Range("B11:FV11"), ActiveSheet.UsedRange.EntireColumn)
.EntireColumn.Hidden = False
.FormulaR1C1 = "=sum(R3C:R10C)"
Set found = GetZeroColumns(.Cells, 0)
End With
If Not found Is Nothing Then found.EntireColumn.Hidden = True
End Sub
Function GetZeroColumns(rng As Range, value As Variant) As Range
Dim firstAddress As String
Dim found As Range
With rng
Set found = .Find(What:=value, LookIn:=xlValues, lookat:=xlWhole)
If Not found Is Nothing Then
firstAddress = found.Address
Set GetZeroColumns = found
Do
Set GetZeroColumns = Union(GetZeroColumns, found)
Set found = .FindNext(found)
Loop While Not found Is Nothing And found.Address <> firstAddress
End If
End With
End Function

We could use a more versatile code to do this, by not hard coding the range of consideration, so that it can be reused in many places. Consider below, the For...Next loop will test each cell in Selection. Selection is the current selected cells. So just select the cells you want the code to run on. If a cell's value equals 0, then the column will be marked for hiding. I'd also not recommend hiding the column one-by-one, it makes the code unnecessarily slow, especially when there are a lot of formulas in the sheet or there are many columns to hide. So what i did is just mark the columns for hiding using the Union function. Then hide them at one go which you can see at the last line of the code.
Sub HideZerosByColumn()
Dim iRng As Range
Dim uRng As Range
Set uRng = Nothing
For Each iRng In Selection
If iRng = 0 And Not IsEmpty(iRng) Then
If uRng Is Nothing Then Set uRng = iRng Else Set uRng = Union(uRng, iRng)
End If
Next iRng
If Not uRng Is Nothing Then uRng.EntireColumn.Hidden = True
End Sub
Before running the code, select the range for consideration.
After running the code

Related

Fixing Macro to Combine Contiguous Values

I'm creating a calendar right now based on months/quarters and I'm on the final steps and one of the last things I need to figure out is how to combine cells with duplicate values and merge them so they flow across the calendar fluidly.
What I have now:
What I want:
I'm a newbie VBA coder but have some experience with C# so I have been looking around and put together this code but I'm not sure if I got the logic right or if it even works properly:
Option Explicit
Public Sub MergeContiguousValues(col As Long)
Dim start As Range
Dim finish As Range
Set start = Cells(1, col)
Set finish = start
Application.DisplayAlerts = False
Do While start <> ""
Do While start = finish.Offset(1, 0)
Set finish = finish.Offset(1, 0)
Loop
If start.Address <> finish.Address Then
Range(start, finish).Merge
Range(start, finish).VerticalAlignment = xlCenter
End If
Set start = finish.Offset(1, 0)
Set finish = start
Loop
Application.DisplayAlerts = True
End Sub
Any suggestions on how to do this? Or where I'm going wrong?
I'm using =IF(ISNUMBER(FIND formulas for the calendar pulling information from separate sheets that carry data from an entry form.
I think this is what you're looking for. I tested it in a blank worksheet with the same value in cells A1 thru D1... Hope this helps!
Option Explicit
Public Sub MergeContiguousValues()
Dim start As Range
Dim finish As Range
Dim sVal As String
Dim fVal As String
'replace Cells(1, 1) with your passed variables
Set start = Cells(1, 1)
Set finish = start
'set values for the starting and finishing cell
sVal = start.Value
fVal = finish.Value
'check each column until the name is no longer the same
Do While sVal = fVal
Set finish = finish.Offset(0, 1)
fVal = finish.Value
Loop
'backup one column
Set finish = finish.Offset(0, -1)
'clear all values and only place value in start range
Range(start, finish).Value = ""
Range(start.Address).Value = sVal
'instead of merging, how about aligning across the start and finish range
Range(start, finish).HorizontalAlignment = xlCenterAcrossSelection
End Sub
I'd use the Areas property of Range object like follows:
Public Sub MergeContiguousValues(calendarColumns As Range, calendarStartRow As Long)
Dim i As Long
Dim area As Range
Application.DisplayAlerts = False
With calendarColumns
For i = calendarStartRow To LastRow(calendarColumns, calendarStartRow)
If WorksheetFunction.CountA(.Rows(i)) > 0 Then
For Each area In .Rows(i).SpecialCells(xlCellTypeFormulas).Areas
With area
.Merge
.VerticalAlignment = xlCenter
End With
Next area
End If
Next i
End With
Application.DisplayAlerts = True
End Sub
and here follows the LastRow() function:
Function LastRow(rng As Range, minRow As Long) As Long
With rng.Parent
With Intersect(.UsedRange, rng.columns).SpecialCells(xlCellTypeFormulas)
LastRow = .Areas(.Areas.Count).Row
End With
End With
If LastRow < minRow Then LastRow = minRow
End Function
as per your example a possible usage could be:
Sub main()
MergeContiguousValues Worksheets("calendar").Range("D:O"), 4
End Sub
where I assumed "calendar" as the name of the worksheet with the calendar: change it as per your needs

Fastest Method to Copy Large Number of Values in Excel VBA

Quite simply, I am wondering what the fastest method of copying cell values from one sheet to another is.
Generally, I'll loop through the cells by column and/or row and use a line such as:
Worksheets("Sheet1").Cells(i,j).Value = Worksheets("Sheet1").Cells(y,z).Value
In other cases where my ranges are not consecutive rows/columns (e.g. I want to avoid overwriting cells that already contain data) I'll either have a conditional inside the loop, or I'll fill an array(s) with row & column numbers that I want to cycle through, and then cycle through the array elements. For example:
Worksheets("Sheet1").Cells(row1(i),col1(j)).Value = Worksheets("Sheet2").Cells(row2(y),col2(z)).Value
Would it be faster to define ranges using the cells I want to copy and the destination cells, then do a Range.Copy and Range.Paste operation? Is it possible to define a range using an array without having to loop through it anyway? Or will it be faster anyway to loop through an array to define a range and then copy-pasting the range instead of equating the cell values by looping?
I feel like it might not be possible to copy and paste ranges like this at all (i.e. they would need to be cells continuous through a rectangular array and pasted into a rectangular array of the same size). That being said, I would think that it's possible to equate the elements of two ranges without looping through each cell and equating the values.
For a rectangular block this:
Sub qwerty()
Dim r1 As Range, r2 As Range
Set r1 = Sheets("Sheet1").Range("A1:Z1000")
Set r2 = Sheets("Sheet2").Range("A1")
r1.Copy r2
End Sub
is pretty quick.
For a non-contiguous range on the activesheet, I would use a loop:
Sub qwerty2()
Dim r1 As Range, r2 As Range
For Each r1 In Selection
r1.Copy Sheets("Sheet2").Range(r1.Address)
Next r1
End Sub
EDIT#1:
The range-to-range method does not even require an intermediate array:
Sub ytrewq()
Dim r1 As Range, r2 As Range
Set r1 = Sheets("Sheet1").Range("A1:Z1000")
Set r2 = Sheets("Sheet2").Range("A1:Z1000")
r2 = r1
End Sub
this is really the same as:
ary=r1.Value
r2.value=ary
except the ary is implicit.
i tried 4 methods, and the NOT obvious (to me) came out:
Option Explicit
Sub testCopy_speed()
Dim R1 As Range, r2 As Range
Set R1 = ThisWorkbook.Sheets(1).Range("A1:Z1000")
Set r2 = ThisWorkbook.Sheets(2).Range("A1:Z1000")
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
Dim t As Single
Dim i&, data(), Rg As Range
ReDim data(R1.Rows.Count, R1.Columns.Count)
For Each Rg In R1.Cells: Rg = Rnd()*100: Next Rg
R1.ClearContents
R1.ClearFormats
r2.ClearContents
r2.ClearFormats
'For Each Rg In R1.Cells: 'if you do this too often , you'll get an error
' With Rg
' .Value2 = Rnd() * 100
' .Interior.Color = Rnd() * 65535
' '.Font.Color = Rnd() * 65535
' End With
'Next Rg
t = Timer
For i = 1 To 100
'r2.Value2 = R1.Value2 '1,71 sec
'R1.Copy r2 '0.74 sec <<<< Winer , but see NOTE.
'data = R1.Value2: r2.Value2 = data '1.78 sec
'For Each Rg In R1.Cells: r2.Cells(Rg.Row, Rg.Column).Value2 = Rg.Value2: Next Rg '54 seconds !!
Next i
Erase data
Set R1 = Nothing
Set r2 = Nothing
Set Rg = Nothing
Debug.Print Timer - t
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Note : i wasn't happy with these results so i tested some more, and if R1 contains many different formating , the R1.copy R2 method will take 10 seconds. So in this case R2=R1would be better by far (6 times faster).
I found this thread looking to speed up transfer of 72 cells from one sheet to another (a data storage sheet to a data entry sheet).
My code looked like this:
t(7)=timer*1000
Dim datasht As Worksheet
Set datasht = WB2.Worksheets("Equipment-Data")
With WB2.Worksheets("Equipment")
.Range("D2").Value = datasht.Cells(datarow, 1)
.Range("D3").Value = datasht.Cells(datarow, 2)
.Range("I7").Value = datasht.Cells(datarow, 3)
...
t(8)=timer*1000
...
.Range("G51").Value = datasht.Cells(datarow,72)
End With
t(9)=timer*1000
Code typed by hand, please forgive any typos.
Getting from t(7) to t(9) took around 600ms. As an aside, I switched from using Application.WorksheetFunction.Vlookup 72 times to determining the appropriate row in the data table with a single datarow=.Cells.Find(...) and it made no perceivable impact in execution time.
I added a timer in the middle and confirmed that each half was taking roughly 300ms which made sense but wanted to make sure there wasn't a particular cell causing issues.
Since most of the time only 1 or a small handful of cells have changed I added a check to see if the data is different before writing and the Sub now runs in about 4ms.
If .Range("D2").Formula <> datasht.Cells(datarow, 1).Formula Then .Range("D2").Value = datasht.Cells(datarow, 1)
Never try to loop over big data set with many rows. Try to copy ranges by columns as much as possible.
Dim lRow As Long
lRow = Sheets("Source").Range("A100000").End(xlUp).Row
Sheets("Target").Range("A1:D" & lRow).Value =
Sheets("Source").Range("G1:J" & lRow).Value
Sub CopyPaste(rPaste As Range, rCopy As Range, Optional val As Boolean = True)
Dim p As Long
Dim r As Long
Dim c As Long
Dim aCalculation As XlCalculation
aCalculation = XlCalc()
On Error GoTo Finally
Try:
If rPaste.Count = 1 Then
r = rPaste.Areas(1).Row - rCopy.Areas(1).Row
c = rPaste.Areas(1).Column - rCopy.Areas(1).Column
For p = 1 To rCopy.Areas.Count
With rCopy.Areas(p)
Set rPaste = Union(rPaste, Cells(.Row, .Column).Offset(r, c).Resize(.Rows.Count, .Columns.Count))
End With
Next 'p
End If
For p = 1 To rPaste.Areas.Count
With Cells(rCopy.Areas(p).Row, rCopy.Areas(p).Column).Resize(Application.min(rCopy.Areas(p).Rows.Count, rPaste.Areas(p).Rows.Count), _
Application.min(rCopy.Areas(p).Columns.Count, rPaste.Areas(p).Columns.Count))
If val Then
If 1 Then 'faster
rPaste.Areas(p) = .Value
Else
.Copy
Cells(rPaste.Areas(p).Row, rPaste.Areas(p).Column).PasteSpecial paste:=xlPasteValues
End If
Else
.Copy Destination:= _
Cells(rPaste.Areas(p).Row, rPaste.Areas(p).Column)
End If 'val
End With
Next 'p
Finally:
XlCalc aCalculation
End Sub
Function XlCalc(Optional aCalculation As Long = 0) As XlCalculation
Dim bCutCopyMode As Boolean
Dim bCleared As Boolean
bCutCopyMode = Application.CutCopyMode
XlCalc = Application.Calculation
Application.EnableEvents = aCalculation <> 0
Application.ScreenUpdating = aCalculation <> 0
'assignment to Application.Calculation clears the clipboard
If aCalculation = 0 Then
bCleared = XlCalc <> xlCalculationManual
If bCleared Then Application.Calculation = xlCalculationManual
Else
If aCalculation = xlCalculationAutomatic Then Application.Calculate
bCleared = XlCalc <> aCalculation
If bCleared Then Application.Calculation = aCalculation
End If
If Not bCleared Then Exit Function
If Not bCutCopyMode Then Exit Function
If Selection Is Nothing Then Exit Function
Selection.Copy 'restore clipboard
End Function
Disabling ScreenUpdating adds 8 ms on my machine
Disabling Calculation adds 6
There is no appreciable difference (in microseconds!) between
.Range(cstrColPreviousPrice & clngFirstRow & ":" & cstrColPreviousPrice & glngLastRow).Value2 = .Range(cstrColPrice & clngFirstRow & ":" & cstrColPrice & glngLastRow).Value2
And
.Range(cstrColPreviousPrice & clngFirstRow & ":" & cstrColPreviousPrice & glngLastRow).Value2 = Application.Transpose(gavarPrice())

multiple Excel comments and validation.add weird behavior

I have quite complex Excel VBA project with sheets containing multiple comments and validations and came over some wierd issue several days ago.
It happened that after adding some additional comments to the sheet validation.add stopped working properly showing comment shape for some random cell right after validation.add execution within the cell under validation.
After investigation and some tests I was able to replicate the issue on an empty worksheet with the following code:
Sub CommentsBug()
Dim rng As Range
Dim i As Long
Dim rngItem As Range
Set rng = ActiveSheet.Range("A1:C25000")
For Each rngItem In rng
rngItem.Cells(1, 1).Value = i
If rng.Comment Is Nothing Then rngItem.AddComment
rngItem.Comment.Text "Comment # " & i
i = i + 1
Next
ActiveSheet.Range("E1").Activate
ActiveCell.Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:="1,2,3,4,5"
End Sub
After code execution I have comment box for a random cell appearing right within the validation cell (cannot put screenshot due to lack of rep).In case I change the last processed cell to C20000 the issue does not appear.
The system is Excel 2013 32-bit Office, Win 7 64.
I will be greatful for any advice and walkaround.
UPDATE AND QUICK FIX:
With the help of BruceWayne it was finally possible to get a quick fix (see below as approved answer). Somehow changing For Each statement to For and addressing separate cell ranges worked.
It really seems to be a bug, see important comments of John Coleman and BruceWayne on its specifics below. Hopefully someone from Microsoft will come across it, I have also posted issue at answers.microsoft.com.
As soon as I already had a worksheet full of data, the following comments update code worked for me in order to get rid of appearing comment box (takes amazingly outstanding amount of time for large sheets - many hours, put the number of your rows/columns instead of 3000/500 in the cycle, remove protect/unprotect statements in case you do not have cell protection):
Public Sub RestoreComments()
Dim i As Long
Dim j As Long
Dim rng As Range
Dim commentString As String
Application.ActiveSheet.Unprotect
Application.ScreenUpdating = False
For i = 1 To 3000
For j = 1 To 500
Set rng = Cells(i, j)
If Not rng.comment Is Nothing Then
commentString = rng.comment.Shape.TextFrame.Characters.Text
'commentString = GetStringFromExcelComment(rng.comment)
'see Update #2
rng.comment.Delete
rng.AddComment
rng.comment.Text commentString
rng.comment.Shape.TextFrame.AutoSize = True
End If
Next j
Next i
Application.ScreenUpdating = True
Application.ActiveSheet.Protect userinterfaceonly:=True
End Sub
UPDATE #2
When performing restoring comments I also came across another issue of trancation of comment string exceeding 255 characters when using comment.Shape.TextFrame.Characters.Text. In case you have long comments use the following code to return comment string:
'Addresses an Excel bug that returns only first 255 characters
'when performing comment.Shape.TextFrame.Characters.Text
Public Function GetStringFromExcelComment(comm As comment) As String
Dim ifContinueReading As Boolean
Dim finalStr As String, tempStr As String
Dim i As Long, commStrLimit As Long
ifContinueReading = True
commStrLimit = 255
i = 1
finalStr = ""
Do While ifContinueReading
'Error handling addresses situation
'when comment length is exactly the limit (255)
On Error GoTo EndRoutine
tempStr = comm.Shape.TextFrame.Characters(i, commStrLimit).Text
finalStr = finalStr + tempStr
If Len(tempStr) < commStrLimit Then
ifContinueReading = False
Else
i = i + commStrLimit
End If
Loop
EndRoutine: GetStringFromExcelComment = finalStr
End Function
The solution was found in the following thread (slightly changed to address the string exactly matching the limit):
Excel Comment truncated during reading
So, after tweaking the code, I found that if you change the For() loop, you can stop the comment from appearing. Try this:
Sub CommentsBug()
Dim rng As Range
Dim i As Long
Dim rngItem As Range
Dim ws As Worksheet
Dim k As Integer, x As Integer
Set ws = ActiveSheet
Application.ScreenUpdating = False
Set rng = ws.Range("A1:C25000")
For k = 1 To 25000
If i > 25000 Then Exit For
For x = 1 To 3
Set rngItem = Cells(k, x)
Cells(k, x).Value = i
If rng.Comment Is Nothing Then rngItem.AddComment
rngItem.Comment.Text "Comment # " & i
rngItem.Comment.Visible = False
rngItem.Comment.Shape.TextFrame.AutoSize = True
i = i + 1
Next x
Next k
ws.Range("E1").Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:="1,2,3,4,5"
Application.ScreenUpdating = True
Application.DisplayCommentIndicator = xlCommentIndicatorOnly
End Sub
Note: This might take a little bit longer to run, but it doesn't give the same random comment popping up as yours does. Also, as for why this works and the other For() loop won't, I have no idea. I suspect it's something to do with the way Excel uses Validation, instead of it being something with the code (but that's pure speculation, perhaps someone else knows what is going on).
This kludge seems to work (although there is no guarantee that the underlying bug won't bubble to the surface somewhere else)
Sub CommentsBug()
Dim rng As Range
Dim i As Long
Dim rngItem As Range
Dim kludgeIndex As Long
Dim kludgeRange As Range
Dim temp As String
Application.ScreenUpdating = False
Set rng = ActiveSheet.Range("A1:C25000")
kludgeIndex = rng.Cells.Count Mod 65536
For Each rngItem In rng
rngItem.Cells(1, 1).Value = i
If i = kludgeIndex Then Set kludgeRange = rngItem
If rngItem.Comment Is Nothing Then rngItem.AddComment "Comment # " & i
i = i + 1
Next
Application.ScreenUpdating = True
ActiveSheet.Range("E1").Activate
ActiveCell.Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:="1,2,3,4,5"
If Not kludgeRange Is Nothing Then
Debug.Print kludgeRange.Address 'in case you are curious
temp = kludgeRange.Comment.Text
kludgeRange.Comment.Delete
kludgeRange.AddComment temp
End If
End Sub
When run like above, kludgeRange is cell $C$3155 -- which displays 9464. If the 25000 is changed to 26000, kludgeRange becomes cell $C$4155, which displays 12464. This is a truly weird kludge where to exorcise the ghost from cell E1 you have to go thousands of cells away.

Select a column by letter from activeCell (without activeCell.EntireColumn)

First and foremost, the below works as expected. I'm trying to make the macro mimic one we have in word. Our word macro will select the entire column simply to display which column is currently being processed (the selection object is not used for any actual processing).
In excel, when I attempt to select the column (activecell.entirecolumn.select) if there is a merged cell it will show multiple columns. I need it only to select the letter column (pretty much the same as clicking the letter at the top) of the active cell. I'm hoping for a method that wont require me to parse the address of the cell if possible (I feel like string parsing is sloppy).
Sub setwidths()
Dim rangeName As String
Dim selectedRange As range
Dim tempRange As range
Dim x As Integer
'If only 1 cell is selected, attempt to find the correct named range
If Selection.Cells.Count = 1 Then
rangeName = Lib.getNamedRange(Selection) 'Built in function from my lib (works I promise)
If rangeName <> "" Then
Application.Goto reference:=rangeName
End If
End If
Set selectedRange = Selection
'Go column by column asking for the width
'Made to mimic a word MACRO's behavior and moving backwards served a point in word
For x = selectedRange.Columns.Count To 1 Step -1
Set tempRange = selectedRange.Columns(x)
tempRange.Cells(tempRange.Cells.Count, 1).Select
'This is where the code should go to select the column
tempRange.ColumnWidth = InputBox("This columns?")
Next
End Sub
Is there anyway to select a column by letter (range("A:A").select for instance) from within an active cell?
Edit:
Record MACRO shows that columns("A:A").select is used when clicking the letter at the top; however, entering that same line into the immediate window will select all columns that merged cells are merged across same as with range("A:A").select and activecell.selectcolumn
Sub NSTableAdjust()
Dim rangeName As String
Dim selectedRange As range
Dim tempRange As range
Dim cellsColor() As Long
Dim cellsPattern() As XlPattern
Dim cellsTaS() As Long
Dim cellsPTaS() As Long
Dim result As String
Dim abort As Boolean
Dim x As Integer
Dim y As Integer
'Delete the block between these comments and run macro on 10x10 grid in excel to test
If Selection.Cells.Count = 1 Then
rangeName = Lib.getNamedRange(Selection)
If rangeName <> "" Then
Application.Goto reference:=rangeName
End If
End If
'Delete the block between these comments and run macro on 10x10 grid in excel to test
Set selectedRange = Selection
ReDim cellsArr(1 To selectedRange.Rows.Count)
ReDim cellsColor(1 To UBound(cellsArr))
ReDim cellsPattern(1 To UBound(cellsArr))
ReDim cellsTaS(1 To UBound(cellsArr))
ReDim cellsPTaS(1 To UBound(cellsArr))
abort = False
For x = selectedRange.Columns.Count To 1 Step -1
Set tempRange = selectedRange.Columns(x)
tempRange.Cells(tempRange.Cells.Count, 1).Select
For y = 1 To UBound(cellsColor)
With tempRange.Cells(y, 1).Interior
cellsColor(y) = .Color
cellsPattern(y) = .Pattern
cellsTaS(y) = .TintAndShade
cellsPTaS(y) = .PatternTintAndShade
.Color = 14136213
End With
Next
result = InputBox("This Column?")
If IsNumeric(result) Then
tempRange.ColumnWidth = result
Else
abort = True
End If
For y = 1 To UBound(cellsColor)
With tempRange.Cells(y, 1).Interior
.Color = cellsColor(y)
.Pattern = cellsPattern(y)
.TintAndShade = cellsTaS(y)
.PatternTintAndShade = cellsPTaS(y)
End With
Next
If abort Then
Exit Sub
End If
Next
End Sub
My current solution to simply shade the cells and then restore their original shading after processing the column.
After an obviously lengthy discussion in the comments on the post. It appears the answer to my question is simply "Not Possible."
The solution I settled on in an attempt to get as close to the "Look" I was searching for is below:
For x = selectedRange.Columns.Count To 1 Step -1
Set tempRange = selectedRange.Columns(x) 'Range of the column
'Our standards dictate the last cell in the range will not be merged
With tempRange.Cells(tempRange.Cells.Count, 1)
.Select 'Selecting here will for excel to make sure the range is in view
'Very simple/basic conditional formatting rule
Set fCondition = .EntireColumn.FormatConditions. _
Add(Type:=xlExpression, Formula1:="=True")
fCondition.Interior.Color = 15123099
'Make sure it is the highest priority rule
fCondition.Priority = 1
End With
'Get user input
result = InputBox("This Column?")
'Delete rule
fCondition.Delete
'Validate user input
If IsNumeric(result) Then
tempRange.ColumnWidth = result
Else
abort = True
End If
If abort Then
Exit Sub
End If
Next

Delete Cells in excel and move contents up based on value

I've got some code working to condense multiple columns in excel, removing any blank cells and shunting the data upwards.
Every cell contains formulae, I did find a code snippet that let me use a specialcells command, but that only removed truly blank cells and not ones that contained a formula, where the outcome would make the cell blank.
This is what I'm currently using, which was an edit of something I found on this site a while ago:
Sub condensey()
Dim c As Range
Dim SrchRng
Set SrchRng = ActiveSheet.Range("B2", ActiveSheet.Range("B208").End(xlUp))
Do
Set c = SrchRng.Find("", LookIn:=xlValues)
If Not c Is Nothing Then c.Delete
Loop While Not c Is Nothing
End Sub
I tried increasing the range on the active sheet to include a second column, but excel just goes nuts, assuming it's trying to do it for every cell in the entire table.
I've then repeated this piece of code for each column that I want to condense.
Now this is great, it does exactly what I want to do, but it is slow as anything, especially when each column can contain up to 200+ rows. Any ideas on how to improve the performance of this, or maybe re-write it using a different method?
This ran in <1sec on 300rows x 3cols
Sub DeleteIfEmpty(rng As Range)
Dim c As Range, del As Range
For Each c In rng.Cells
If Len(c.Value) = 0 Then
If del Is Nothing Then
Set del = c
Else
Set del = Application.Union(del, c)
End If
End If
Next c
If Not del Is Nothing Then del.Delete
End Sub
I found that using AutoFilter on each column was faster than looping through each cell in the range or "Find"ing each blank cell in the range. Using the code below and some sample data (3 columns with approximately 300 rows of blank and non blank cells), on my machine it took 0.00063657 days. Using the loop through each cell method, it took 0.00092593 days. I also ran your code on the sample data, and it took a lot longer (I didn't let it finish). So far, the method below yields the quickest results, though I imagine someone will find a faster method.
It appears that the delete method is the biggest bottleneck. It may be fastest to filter the non-blank cells and paste them into a new range, and then delete the old range once you're finished.
Sub condensey2()
Dim c As Range
Dim tbl As Range, tblWithHeader As Range, tblEnd As Range, delRng As Range
Dim i As Long
Dim maxRows As Long
Dim t As Double
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
ActiveSheet.Calculate
maxRows = ActiveSheet.Rows.Count
ActiveSheet.AutoFilterMode = False
With ActiveSheet
Set tblEnd = Range(.Cells(maxRows, 1), .Cells(maxRows, 3)).End(xlUp)
Set tbl = Range(.Cells(2, 1), Cells(tblEnd.Row, 3))
End With
t = Now()
Set tblWithHeader = tbl.Offset(-1).Resize(tbl.Rows.Count + 1)
i = 1
For i = 1 To tbl.Columns.Count
With tblWithHeader
.AutoFilter
.AutoFilter field:=i, Criteria1:="="
End With
Set delRng = tbl.Columns(i).Cells.SpecialCells(xlCellTypeVisible)
ActiveSheet.AutoFilterMode = False
delRng.Delete xlShiftUp
'redefine the table to make it smaller to make the filtering efficient
With ActiveSheet
Set tblEnd = Range(.Cells(maxRows, 1), .Cells(maxRows, 3)).End(xlUp)
Set tbl = Range(.Cells(2, 1), Cells(tblEnd.Row, 3))
End With
Set tblWithHeader = tbl.Offset(-1).Resize(tbl.Rows.Count + 1)
Next i
t = Now() - t
Debug.Print Format(t, "0.00000000")
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
End Sub