Excel VBA Wrong copy with Advanced Filter - vba

Im trying to get a list of unique data through the Advanced Filter option in Excel.
So, I recorded the macro I wanted to do and I got this as the code:
Sheets("Totaal").Range("A3:A65000").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range( _
"D2"), Unique:=True
This should be fine as far as I know because I did this a few times. Problem is, with this one, he only copies the first element to the selected Range. (I tried with actually giving it a range but that didn't change anything).
So, only copying first element not the whole array. If I do it manually it works.

Stumbled onto this one, very interesting problem. I created a test sheet to mimic yours that looked like:
The following code dropped all of the unique entries into col D starting at row 2:
Option Explicit
Sub Test()
Dim TotaalSheet As Worksheet
'set worksheet for easy reference
Set TotaalSheet = ThisWorkbook.Worksheets("Totaal")
'apply the advanced filter to get uniques
With TotaalSheet.Range("A3:A65000")
.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=TotaalSheet.Range("D2"), Unique:=True
End With
End Sub
Here are the results:

nothing is copied if the one of many empty cells on top of the range.

Related

Apply dynamic AutoFilter across worksheets based on filter on 1st sheet

I am trying to create a dynamic autofilter in excel which could be applied across all the sheets (total: 5) in the excel based on what filter the user selects in 1st sheet.
For Example: I have 5 sheets in an excel all with different columns and 1 "primary key" column.
Sheet1 = Headers - PK, Col1, Col2 ...
Sheet2 = Headers - PK, Col6, Col7 ...
.
.
Sheet5 = Headers - PK, Col20, Col21 ...
The filter should work like, the user creates a filter on the column headers on sheet1 via selecting the header and then Home > Sort & Filter. Once we have filters on header, the user can then selects multiple values (check boxes) on the 1st column (PK) and now wants that filter to be applied across all sheets on the PK column.
I was able to create an autofilter but it is actually static and does not change based on user input.
Version: MS OFfice Excel 2010
VB Macro:
Sub apply_autofilter_across_worksheets()
Dim xWs As Worksheet
On Error Resume Next
For Each xWs In Worksheets
xWs.Range("A1").AutoFilter 1, "=Sheet1!$A$1"
Next
End Sub
I am relatively new to excel. In this case, the column I am filtering on, on the first sheet is a number, so the array I am trying to use in the criteria seems to be a text.
Not exactly the solution I was looking for but I think I can get the end-user work with this setup. I ended up creating a separate sheet altogether which would be used as a reference filter. To populate the data in this sheet, the user can copy whatever data he need from sheet1 to filter upon for example - first 5 records he want to filter upon.
Populate the REFERENCE sheet with the data he needs to filter upon.
Pre-Created, dynamic named range. In this case - Critlist
Run the macros below and filter on all sheets.
Note: In this case, I puposefully used the 2nd column ($B) from the REFERENCE sheet to create this dynamic list since my filter will loop through all the sheets and filter on 1st column. Once it reaches the reference sheet, it wont find the 1st column and so can exit cleanly.
I used the reference below to help on this situation. Couple of lessons learned -
1. The array used in the link is myarray which works fine for their use case as per the sample file, but I had to use myarray(i, 1) when I was looping though it - Reference #3. Not sure why would it need two-dimensional array to work.
2. The array created in Reference #1 is Range of values - sheet.Range("").Value whereas in my case I created an array as sheet.Range("") i.e. without the value.
VB Macros:
Option Explicit
Public Sub apply_autofilter_across_worksheets()
Dim myarray As Variant, xWs As Worksheet, wsL As Worksheet, i As Long
Set wsL = Worksheets("REFERENCE")
myarray = wsL.Range("CritList")
For i = LBound(myarray, 1) To UBound(myarray, 1)
myarray(i, 1) = CStr(myarray(i, 1))
Next
For Each xWs In Worksheets
xWs.Range("$A$1").AutoFilter _
Field:=1, _
Criteria1:=Application.WorksheetFunction.Transpose(myarray), _
Operator:=xlFilterValues
Next
End Sub
EDIT:
I think I have found a better way doing it even without creating an additional sheet (the way I was hoping to do ideally). It is the same concepts with creating the same list, however in this case, instead of creating it separately, I created it on the 1st sheet I want to drive the selection/filtering from. Then by using .SpecialCells(xlCellTypeVisible).EntireRow we can only select what is visible on the sheet i.e. the filtered criteria and then apply it to the remaining sheets in the loop. In this case, even if it overwrites the 1st sheet, it should not be a problem since it is already filtered the way we want. This should also be repeatable since every time the reference list changes, it should dynamically update the rest of the filters by running the macros.
VB Code:
Option Explicit
Public Sub Filter_RFX()
Dim myarray As Variant, xWs As Worksheet, wsL As Worksheet, i As Long
Set wsL = Worksheets("Sheet1")
myarray = wsL.Range("CritList").SpecialCells(xlCellTypeVisible).EntireRow
For i = LBound(myarray, 1) To UBound(myarray, 1)
myarray(i, 1) = CStr(myarray(i, 1))
Next
For Each xWs In Worksheets
xWs.Range("$A$1").AutoFilter _
Field:=1, _
Criteria1:=Application.WorksheetFunction.Transpose(myarray), _
Operator:=xlFilterValues
Next
End Sub
Reference:
1. http://www.contextures.com/xlNames01.html#videodynamic
http://blog.contextures.com/archives/2010/12/15/excel-autofilter-with-criteria-in-a-range/
http://www.java2s.com/Code/VBA-Excel-Access-Word/Data-Type/UseLBoundandUBoundinforstatement.htm

How do I freeze the active row in excel with VBA?

Like the title says, I need to write a VBA code that copies the entire row i selected and pastes only the values so the results cannot be changed afterwards.
I have already managed to do this for the ActiveCell in the last file I worked on, but I only had to change one cell then. This is the code I used:
Sub Freeze()
ActiveCell.Copy
ActiveCell.PasteSpecial Paste:=xlPasteValues
End Sub
However, for this new file I have to copy the entire row and i don't want to select each individual cell. When I use this in the new file, it only works on the first cell. How can I make it work for the entire row?
Thanks.
Use the simple code below (try to avoid Selecting cells, and active cells if possible):
Sub Test()
Call Freeze(3, 7)
Call Freeze(4, 8)
End Sub
Sub Freeze(rowsource As Long, rowdest As Long)
Rows(rowsource).Copy
Rows(rowdest).PasteSpecial Paste:=xlPasteValues
End Sub
Paste:=xlPasteValues
You do not need to copy paste values unless you are trying to copy formats as well.
Try this. Replace the sheet names with the relevant sheet names. Also Replace X,Y with the relevant row numbers. If you are using multiple rows then change Rows(X) to Range("X:X") and similarly Range("Y:Y")
Thisworkbook.Sheets("Sheet1").Rows(X).Value = _
Thisworkbook.Sheets("Sheet2").Rows(Y).Value

Copy and paste values and not formulas

Writing macro for the first time, I have to copy only cell values to another and which I got it working, however, I am not sure, how to copy entire column without specifying range since range may be different every time. Here, I am trying with a range which is working, but I want it to check values of cell for that column and until it finds a value copy/paste to the another column.
Here is the code I have so far:
Sub CopyingCellValues()
Range("E2:E7").Copy
Range("C2:C7").PasteSpecial xlPasteValues
End Sub
Thanks.
Simple Columns copy will be...
Sheets("Sheet Name").Columns(1).Copy Destination:=Sheets("Sheet Name").Columns(2)
Helpful info at MSDN on Getting Started with VBA in Excel 2010
Edit:
With out the formula, Try
Sub CopyingCellValues()
Range("E:E").Value = _
Range("C:C").Value
End Sub
Sub ValueToValue()
[E:E].Value = [C:C].Value
End Sub

Get the cell reference of the value found by Excel INDEX function

The Problem
Assume that the active cell contains a formula based on the INDEX function:
=INDEX(myrange, x,y)
I would like to build a macro that locates the value found value by INDEX and moves the focus there, that is a macro changing the active cell to:
Range("myrange").Cells(x,y)
Doing the job without macros (slow but it works)
Apart from trivially moving the selection to myrange and manually counting x rows y and columns, one can:
Copy and paste the formula in another cell as follows:
=CELL("address", INDEX(myrange, x,y))
(that shows the address of the cell matched by INDEX).
Copy the result of the formula above.
Hit F5, Ctrl-V, Enter (paste the copied address in the GoTo dialog).
You are now located on the very cell found by the INDEX function.
Now the challenge is to automate these steps (or similar ones) with a macro.
Tentative macros (not working)
Tentative 1
WorksheetFunction.CELL("address", ActiveCell.Formula)
It doesn't work since CELL for some reason is not part of the members of WorksheetFunction.
Tentative 2
This method involves parsing the INDEX-formula.
Sub GoToIndex()
Dim form As String, rng As String, row As String, col As String
form = ActiveCell.Formula
form = Split(form, "(")(1)
rng = Split(form, ",")(0)
row = Split(form, ",")(1)
col = Split(Split(form, ",")(2), ")")(0)
Range(rng).Cells(row, CInt(col)).Select
End Sub
This method actually works, but only for a simple case, where the main INDEX-formula has no nested subformulas.
Note
Obviously in a real case myrange, x and ycan be both simple values, such as =INDEX(A1:D10, 1,1), or values returned from complex expressions. Typically x, y are the results of a MATCH function.
EDIT
It was discovered that some solutions do not work when myrange is located on a sheet different from that hosting =INDEX(myrange ...).
They are common practice in financial reporting, where some sheets have the main statements whose entries are recalled from others via an INDEX+MATCH formula.
Unfortunately it is just when the found value is located on a "far" report out of sight that you need more the jump-to-the-cell function.
The task could be done in one line much simpler than any other method:
Sub GoToIndex()
Application.Evaluate(ActiveCell.Formula).Select
End Sub
Application.Evaluate(ActiveCell.Formula) returns a range object from which the CELL function gets properties when called from sheets.
EDIT
For navigating from another sheet you should first activate the target sheet:
Option Explicit
Sub GoToIndex()
Dim r As Range
Set r = Application.Evaluate(ActiveCell.Formula)
r.Worksheet.Activate
r.Select
End Sub
Add error handling for a general case:
Option Explicit
Sub GoToIndex()
Dim r As Range
On Error Resume Next ' errors off
Set r = Application.Evaluate(ActiveCell.Formula) ' will work only if the result is a range
On Error GoTo 0 ' errors on
If Not (r Is Nothing) Then
r.Worksheet.Activate
r.Select
End If
End Sub
There are several approaches to select the cell that a formula refers to...
Assume the active cell contains: =INDEX(myrange,x,y).
From the Worksheet, you could try any of these:
Copy the formula from the formula bar and paste into the name box (to the left of the formula bar)
Define the formula as a name, say A. Then type A into the Goto box or (name box)
Insert hyperlink > Existing File or Web page > Address: #INDEX(myrange,x,y)
Adapt the formula to make it a hyperlink: =HYPERLINK("#INDEX(myrange,x,y)")
Or from the VBA editor, either of these should do the trick:
Application.Goto Activecell.FormulaR1C1
Range(Activecell.Formula).Select
Additional Note:
If the cell contains a formula that refers to relative references such as =INDEX(A:A,ROW(),1) the last of these would need some tweaking. (Also see: Excel Evaluate formula error). To allow for this you could try:
Range(Evaluate("cell(""address""," & Mid(ActiveCell.Formula, 2) & ")")).Select
This problem doesn't seem to occur with R1C1 references used in Application.Goto or:
ThisWorkbook.FollowHyperlink "#" & mid(ActiveCell.FormulaR1C1,2)
You could use the MATCH() worksheet function or the VBA FIND() method.
EDIT#1
As you correctly pointed out, INDEX will return a value that may appear many times within the range, but INDEX will always return a value from some fixed spot, say
=INDEX(A1:K100,3,7)
will always give the value in cell G3 so the address is "builtin" to the formula
If, however, we have something like:
=INDEX(A1:K100,Z100,Z101)
Then we would require a macro to parse the formula and evaluate the arguments.
Both #lori_m and #V.B. gave brilliant solutions in their own way almost in parallel.
Very difficult for me to choose the closing answer, but V.B. even created Dropbox test file, so...
Here I just steal the best from parts from them.
'Move to cell found by Index()
Sub GoToIndex()
On Error GoTo ErrorHandler
Application.Goto ActiveCell.FormulaR1C1 ' will work only if the result is a range
Exit Sub
ErrorHandler:
MsgBox ("Active cell does not evaluate to a range")
End Sub
I associated this "jump" macro with CTRL-j and it works like a charm.
If you use balance sheet like worksheets (where INDEX-formulas, selecting entries from other sheets, are very common), I really suggest you to try it.

How do I count the number of non-zeros in excel?

I am trying to make a macro that will go through a whole workbook and count the number of days a employee worked. The sheets have the work broken out in days so all T have to find is the days that are not zero. I have tried to use COUNTIF(A11:A12,">0") and I get the error Expected : list separator or ). I am using a For Each loop to work through the sheets. I would like to put all the information on a new sheet at the end of the workbook with the name of the employee and the days worked. I am very new to visual basic but am quite good with c#.
I now have gotten this far
Option Explicit
Sub WorksheetLoop2()
' Declare Current as a worksheet object variable.
Dim Current As Worksheet
Dim LastColumn As Integer
If WorksheetFunction.CountA(Cells) > 0 Then
' Search for any entry, by searching backwards by Columns.
LastColumn = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
End If
' Loop through all of the worksheets in the active workbook.
For Each Current In Worksheets
Current.Range("A27") = Application.WorksheetFunction.CountIf(Current.Range(Cells(11, LastColumn), Cells(16, LastColumn)), ">0")
Current.Range("A28") = Application.WorksheetFunction.CountIf(Current.Range("Al17:Al22"), ">0")
Next
End Sub
When I run this I get an error saying method range of object'_worksheet' failed. I also haven't been able to find a way to get the information all on the summary sheet.
VBA Solution, in light of your last comment above.
Good VBA programming practice entails always using Option Explicit with your code, that way you know when you don't have variables declared correctly, or, sometimes, if code is bad! In this case you would have picked up that just writing A27 does not mean you are returning the value to cell A27, but rather just setting the value you get to variable A27. Or maybe you wouldn't know that exactly, but you would find out where your problem is real quick!
This code should fix it for you:
Option Explicit
Sub WorksheetLoop2()
'Declare Current as a worksheet object variable.
Dim Current As Worksheet
' Loop through all of the worksheets in the active workbook.
For Each Current In Worksheets
Current.Range("A27") = Application.WorksheetFunction.CountIf(Current.Range("A11:A12"), ">0")
Next
End Sub
In case it helps, Non-VBA solution:
Assuming you have a Summary sheet and each employee on a separate sheet, with days in column A and hours worked in column B, enter formula in formula bar in B1 of Summary and run down the list of names in column A.