Arrange columns based on the order of values in an array - and buttons disappearing - vba

I have this code which is looking in a column (On a different sheet at XFD1) and creating an array from the values in that column. Then it is searching for those values one at a time across a row on the current sheet. When it finds a match, it cuts the column and inserts it at the location that corresponds to the order of the values in the array.
I'm not getting any compile errors. I placed a button (not ActiveX) on the worksheet and used it to execute the code. Here is what I see:
Nothing appears to happen. Columns are not moved at all.
The computer is obviously "thinking " because the whirly-gig is spinning away.
And here is the Mysterious part - The button disappears! it never comes back. I placed several buttons on the worksheet and tried them all. The button disappears every time.
I really need to get this working. All I want is to reorder the columns to the same order as my list on the other sheet (95 items in the list). I thought this code would do it but I seem to have entered the Twilight Zone and things are not as they seem (at least from my perspective)!
Here it is:
Sub Reorder_Columns()
Dim arrColumnOrder(1 To 95) As String
Dim index As Integer
Dim Found As range
Dim tick As Integer
For index = 1 To 95
arrColumnOrder(index) = UserFormDropDownDataSheet.range("XFD1")
Next index
Application.ScreenUpdating = False
tick = 1
For index = LBound(arrColumnOrder) To UBound(arrColumnOrder)
Set Found = Rows("1:1").Find(arrColumnOrder(index), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
If Not Found Is Nothing Then
If Found.column <> tick Then
Found.EntireColumn.Cut
Columns(tick).Insert Shift:=xlToRight
Application.CutCopyMode = False
End If
tick = tick + 1
End If
Next index
Application.ScreenUpdating = True
End Sub

The answer to the question concerning what was wrong with my original code is:
First, I was trying to set the size of the array but should have been working with a dynamic array as I expect the data in my array column will grow as I add more columns to the sheet I am trying to sort. So, Dim arrColumnOrder(1 To 95) As String should have been Dim arrColumnOrder As Variant.
I was then trying to iterate over my array with
For index = 1 To 95
arrColumnOrder(index) = UserFormDropDownDataSheet.range("XFD1")
Next index
Which of course is all wrong. I replaced this with
arrColumnOrder = UserFormDropDownDataSheet.range("XFD1:XFD95").Value
Then, in
Set Found = Rows("1:1").Find(arrColumnOrder(index), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
Should have been "... Find (arrColumnOrder(index, 1)..."
The answer to why the button was moving is that I did not Set the format option of the button (right click the button, Format Control>Properties>Select "Don't move or size with cells.") So when things were moving (and weirdly because my code was all wrong) The button moved with the cell when the column was copied and pasted.
Here is my final code and it works and does exactly what it is expected to do. Namely, it creates an array from the data in the range "XFD1:XFD95" (on a separate worksheet where I have the column headers stored in the proper order), then it sorts the columns in the active worksheet to match the order of the array. I did not want to explicitly call a sheet name as this will run on varying sheets. Using Find as opposed to Match, works just fine for me as this is not a huge amount of data I'm dealing with so speed is not an issue.
Sub Reorder_Columns()
Dim arrColumnOrder As Variant
Dim index As Interger
Dim Found As range
Dim tick As Integer
arrColumnOrder = UserFormDropDownDataSheet.range("XFD1:XFD95").Value
Application.ScreenUpdating = False
tick = 1
For index = LBound(arrColumnOrder) To UBound(arrColumnOrder)
Set Found = Rows("1:1").Find(arrColumnOrder(index, 1), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
If Not Found Is Nothing Then
If Found.column <> tick Then
Found.EntireColumn.Cut
Columns(tick).Insert Shift:=xlToRight
Application.CutCopyMode = False
End If
tick = tick + 1
End If
Next index
Application.ScreenUpdating = True
End Sub
For me, one of the big lessons here is to not try to write code when I have only slept for two hours! I was really tired and making silly mistakes because I was not thinking clearly. I got a good night's rest and then this morning I was easily able to see where I went wrong.

Related

Normalise Header on all worksheets VBA

I'm new to VBA and have been playing around with the basics.
What I'm tasked to do is to extract data from a certain row based on the header and column's data. Example, if column under the header "ENG JOBSCOPE" <> "", then extract row of that data.
However, i'm stuck at a point where when the macro loops thru all the worksheets, if the criteria that i want to find using range.find could not find, it'll give me error 91.
I've read up about using normalise but i can't seem to make it work.
Currently i'm using this code
J = 1
For Each ws In x.Worksheets
For Each wks In y.Worksheets
With x.Worksheets(ws.Name)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
For i=2 to Lastrow
EJOB = Range("A1:DE1").Find(What:="ENG JOBSCOPE", LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=True, SearchFormat:=False).Column
'Error 91 comes from the above line ^^^
EJobs = ws.Cells(i, EJOB).Value
If EJobs <> "" then
x.Sheets("ID").Rows(i).Copy
y.Sheets("ID").Range("A" & j).PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
J=J+1
End if
Next i
Next wks
Next ws
End sub
"ENG JOBSCOPE" is a header. However, some of the worksheet consist of "ENG JOB SCOPE".
Is there another method where i can use to make it so that it'll find the column number regardless of space or capitalization in between?
Also, some of the worksheet doesn't consist of the "ENG JOBSCOPE". Is there a way for the code to continue searching without it stopping with error 91?
I've tried using on error goto next, but the data gets jumbled up.
I hope what i typed is sufficient enough or clear enough. If it isn't clear enough please tell me what is needed to type as i'm new to this forum.
Thank you very much in advance!
use wildcard.
EJOB = Range("A1:DE1").Find(What:="ENG*JOB*SCOPE", LookIn:=xlValues, _
LookAt:=xlWhole, MatchCase:=True, SearchFormat:=False).Column
If you want Find to work regardless of capitalisation, type MatchCase:=False.

How to expand a group in Excel by using Hyperlink(or by maybe assigning Macro to Hyperlink)

I have a table at the top of my sheet and this table has a different section names.
I'd like to insert a hyperlink to these section names to go and open it's group below when I click them.
Please Refer to the view of my table and sections as default (Collapsed)
I could create a macro which:
Expands all groups
Goes to the Section that I clicked,
Collapses all groups
Only opens the group on active cell,
But assigning this macro to ~20 different sections increases the file size.
After some search I found this on SO: Excel: Assign a macro to a hyperlink? So maybe there is a way to connect this two method?
How this can be solved?
I'd suggest creating a master sheet with the "group" table and any rollups you need. The subsequent sheets could have all the "section" data on them. This has the added benefit of being more scaleable.
Is it strictly necessary to have all the information on the same sheet? This is pretty much why Excel has multiple sheets. Using multiple sheets would also allow you to use standard hyperlinks.
However, if you would like some VBA to get you closer, consider the code below. This grabs the value form the active cell, then searches for the next cell with that value. If the section with the found cell is collapsed, it expands it and visa versa.
Sub OpenSection()
Dim x As String
x = ActiveCell.Value
Dim y As String
y = Cells.Find(What:=(x), After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Address
'Range("b1").Value = y
With ActiveSheet
With .Range(y).EntireRow
If .ShowDetail = False Then
.ShowDetail = True
Else
.ShowDetail = False
End If
End With
End With
End Sub

Removing all $ from formulas in Excel

This is a hack-y Excel question, certainly not standard procedure. Apologies in advance.
Inside a workbook, I have a financial model. It spans three sheets: Revenue and Expense, which both read information from Inputs. Together, the sheets form a complete model.
I want to expand my workbook by creating a duplicate model alongside my first one. The idea is to have side-by-side scenarios that I can compare. 2 sets of inputs, 2 sets of expense calcs, and 2 sets of revenue calcs, each set side-by-side on its respective tab.
Normally, I would just copy the formulas over and 'bam' I've got a duplicate model. Unfortunately, I can't do this because I used a ton of $ characters, locking my cell references. Copying the formulas in Revenue off to the right wouldn't change which cells the formulas reference on Inputs tab. The model is large enough that it would take hours to remove the cell reference locks from each formula manually.
My current plan is to use VBA to remove all of the $ characters from formulas and then go ahead with the copy pasting method.
Will this work?
How can I remove a specific character from formulas using VBA?
You can press ctrl+' this will change all the cells from showing their value to showing their formula. In this view you can press ctrl+H to replace all $ with nothing and click "Replace All".
So no need to code it all in vba, which would have been possible too, but probably a bit more complicated.
This will remove all the dollar signs you wanted to remove.
Pay attention though if you make any edits extrapolating formulas in the new dollar-sign-less sheet, that it will probably be incorrect for it will also extrapolate the set references which should contain a $.
For removing the $ in external link paths in Excel -
Sub ExtLinks_RelativePaths()
This macro converts external links in selected cells to relative links by removing the $ from the cell reference
' Source: todd.kluge#merrillcorp.com
Dim myCells As Range, curCell As Range
Dim myVal As Boolean
Application.DisplayAlerts = False
Application.EnableEvents = False
Application.ScreenUpdating = False
Set myCells = ActiveSheet.UsedRange
On Error Resume Next
For Each curCell In myCells
curCell.Select
myVal = IsFormula(curCell)
If myVal = True Then
With Selection
.replace What:="$", Replacement:="", LookAt:=xlPart, SearchOrder:= _
xlByColumns, MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False
End With
End If
Next curCell
Application.DisplayAlerts = True
Application.EnableEvents = True
Application.ScreenUpdating = True
MsgBox ("Formulas on the active sheet now have relative references.")
End Sub
Function IsFormula(cell_ref As Range)
IsFormula = cell_ref.HasFormula
End Function

Find a value from one worksheet in a designated range in another

Intent
It will consist of 11 identical Worksheets (10 for data entry specific areas of the site being worked on, and 1 "Master" which gathers the totals)
The Master Worksheet is where the Start Date is changed. When the Start Date is changed it is reflected in the 10 data entry Worksheets. There are numeric values as well showing how far away the Start Date is.
When the Start Date is changed, the values need to move with the Start Date (i.e. if the Start Date is January 5 and there is already data on the data entry worksheets, if the Start Date is changed to January 7 then all data on all worksheets will need to move to the right by 2)
Intended Process
I was able to get the first two functions working, however it's the last one that's causing some grief.
What I had in mind was a procedural copy-paste of sorts. When the Start Date is changed it would go to the first data entry worksheet and copy the current header settings to a "Transfer" Worksheet, preserving the original date settings for that worksheet. It would then delete the data in the data entry worklist.
The next step was to go to the first of the data entry Worksheets (Codenames in the background start with "Sz"), match the first numeric value of the data entry to the Transfer worksheet, retrieve the data and paste the column data into it's new location.
When it's all done with the data entry worksheet it would then clear out the "Transfer" worksheet, move to the next data entry worksheet, and repeat the process.
Problem
Unfortunately, the code I have written is saying it is finding the numeric values, when that numeric value doesn't exist. And then it sometimes has an error message stating "Code execution has been interrupted".
I have been working on this for about fifteen hours overtime, in addition to about a full week. I have googled countless potential solutions, and tried many workarounds, but am officially at a dead end. I have mostly taught myself through other people's examples, so I'm not an expert in Excel VBA.
If I can get the matching functions working correctly, I believe I should be able to handle the rest, but suggestions on more efficient methods are more than welcome.
I don't use Forums much, but I'll try to paste the code below.
Please let me know what other information I could provide.
Edit: Here is the sample of the Workbook. To run the function you will need to be on the "Plant" worksheet (Sz001): Dropbox Link
Code:
Sub Test()
Dim sh As Worksheet, flg As Boolean
For Each sh In Worksheets
'FUNCTIONAL: If sh.CodeName Like "Sz0*" Then 'flg = True
If sh.CodeName = "Sz001" Then 'Isolating a single Worksheet for testing
'Copy original values and location to Transfer Worksheet
'DISABLED THIS SECTION WHILE TESTING
'sh.Select
'ActiveSheet.Range("H8:ABI460").Copy
'Worksheets("Transfer").Select
'ActiveSheet.Range("H8").PasteSpecial xlPasteValues
'Begin Matching Loop -THIS IS WHERE THE ISSUES ARE HAPPENING
Dim xlRange As Range 'Current sh Range
Dim xlSheet As Worksheet 'Current sh Worksheet
Dim xlCell As Range 'Cell function is currently looking at
Dim x As Range
Set xlSheet = sh
Set xlRange = xlSheet.Range("H6:ABI6")
For Each xlCell In xlRange
Set x = ActiveSheet.Cells.Find(what:=xlCell, after:=Worksheets("Transfer").Range("G6"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByColumns, SearchDirection:=xlNext)
If Not x Is Nothing Then
MsgBox Cells(xlCell.Row, xlCell.Column) & "Found"
Else
MsgBox Cells(xlCell.Row, xlCell.Column) & "Not Found"
End If
Next xlCell
End If
Next
End Sub
Tested:
Option Explicit
Public Sub Test()
Const WS_TR As String = "Transfer" 'Sheet Transfer
Const WS_RNG As String = "H6:ABI6" 'row 6 on both sheets
Dim wsSz As Worksheet, wsTr As Worksheet, cel As Range
Dim found As Range, row6Sz As Range, row6Tr As Range
Set wsSz = Sz001 'Code Name for the sheet "Sz001"
Set wsTr = Worksheets(WS_TR)
Set row6Sz = wsSz.Range(WS_RNG) 'searched values
Set row6Tr = wsTr.Range(WS_RNG) 'search area
For Each cel In row6Sz 'searched values
Set found = row6Tr.Find(what:=Val(cel.Value2), LookIn:=xlValues, _
LookAt:=xlWhole, SearchFormat:=False, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext)
Debug.Print cel.Value2 & IIf(Not found Is Nothing, " Found", " Not Found")
Next
End Sub
.
Note:
I replaced the MsgBox with Debug.Print
For results press Ctrl+G, or View -> Immediate Window

Excel VBA; Referencing Variable Ranges

I'm trying to automate a process that takes a monthly report and generates an exception report based on the data. Since the volume of the data in the report varies from month to month, i need to account for that. What methodology is best for referencing a variable range?
For example, instead of referencing the range A1:F7087, i want to reference the entire range that includes any data. For as simple as this appears to be, I haven't been able to find any guidance on it. Appreciate any input. Thanks
Dim rng As Range
Set rng = Range(Range("A1"), Range("A1").SpecialCells(xlLastCell))
This will set rng to contain all cells up to last filled, it will also contain all empty rows and columns.
You should also read this, important part from this article about xlLastCell:
Depending on what you are trying to accomplish, this cell may not be the cell that you are actually looking for. This is because, for example, if you type a value into cells A1,A2, and B1, Excel considers the last cell to be B2, which could have a value or not...
There are pros and cons with just about any method you choose to reference the dynamic block of data you wish to include with your report.
The caveat that comes with .SpecialCells(xlLastCell) is that it may encompass a cell that was previously used but is no longer within the scope of the data. This could occur if your data shrinks from one month to the next although recent service packs and updates provided for Excel 2010/2013 will shrink the rogue last cell through saving the workbook. I'm sure many of us have at one time or another mistyped a value into AZ1048576 and had to jump through hoops getting Excel to internally resize the extents of the actual data. As mentioned, with later versions of Excel, this problem is all but a footnote in history.
One last thing to note is that cells formatted as anything but plain-Jane General will halt the shrink so if last month's report had a formatted Subtotal line 50 rows below where it is this month, the .SpecialCells(xlLastCell) will be referencing an area 50 rows too long.
If you have a contiguous block of data with some blank cells possible but no fully blank rows or columns that segregate your data into islands then I prefer the following approach.
With sheets("Sheet1")
Set rng = .Cells(1, 1).CurrentRegion
End With
The area referenced by the above code can be demonstrated by selecting A1 and tapping Ctrl+A once (twice is A1:XFD1048576). The cells selected will be a rectangle encompassing the last column with data and the last row with data as the extents. Previously used cells and cells that have retained formatting from previous reports have no effect; only cell values and formulas. However, it must be emphasized that the island of data stops at the first fully blank row or column.
The last method I will mention is to position in (aka .Select or otherwise start at) A1 and use .Find with a wildcard searching backwards so that it ends up starting at XFD1048576 and searching toward A1 for the first value or formula it can find first by row then repeated by column.
Dim lr As Long, lc As Long, rng As Range
With Sheets("Sheet3")
lr = .Cells.Find(What:=Chr(42), After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
lc = .Cells.Find(What:=Chr(42), After:=.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Set rng = .Cells(1, 1).Resize(lr, lc)
End With
This is really the only true method of getting an accurate representation of your data block but its thoroughness is not usually necessary.
Sub highlight()
Dim obj As Range
For Each obj In Sheet1.UsedRange
If obj.Value = Range("A1").Value Then
obj.Interior.Color = vbYellow
Else
obj.Interior.Color = vbWhite
End If
Next obj
End Sub