Union function in VBA - excel

I am trying to filter my data, and add the desired data to the Range. I have set my desired range to null and initiated a search in a particular column for a particular keyword, which if found adds the entire row in my Range.
Column 'E' is been searched for a Value "Desired Value", if found the entire row is added to the PTRange, which is initially set to Nothing(is Empty).
Dim rng As Range
Dim PTRange As Range
Dim count As Long
count = 1
Set PTRange = Nothing
Set rng = Range("E2:E20")
For Each cell In rng
If cell.Value = ("Desired Value") Then
PTRange = Application.Union(Range(PTRange), Range(Rows(count)))
End If
count = count + 1
Next cell
But the compiler shows that there is something wrong in the code where Union method is used.
UPDATE :
Initialization of PTRange and the following change
PTRange=Union(PTRange,Rows(count))
worked for me. Thanks

It is hard to get you to your desired goal as I'm a little unclear what that is but the below can serve as both assistance on your code with a possible solution following.
Firstly I would recommend you always have Option Explicit turned on especially if calling out to Stack Overflow for assistance.
Option Explicit forces you to declare all variables, in this instance it would be of help as you cell as a variable which can appear ambiguous. When it is not declared, it starts its life as a variant and is defined to a specific format on first use, in this case the compiler declared cell as a Range.
It also helps encourage better coding and to understand what is happening in the code more. To turn it on, type Option Explicit at the very top of the code pane (before any procedures or variable declarations), also while in the VBE (Visual Basic Editor) click 'Tools' > 'Options' and ensure 'Require Variable Declaration' is ticked.
Below are some comments within the code you have, these should help you understand the issues you are facing.
Dim rng As Range
Dim PTRange As Range
Dim count As Long
count = 1
'If the range is nothing then it can not be used, it must be started as
'something
Set PTRange = Nothing
Set rng = Range("E2:E20")
'This is where declaring 'cell' would help understanding, it will become a
'Range at the VBE discretion\decision based on what you are trying to do
'with it. Even when looking at a single cell, you do so through a Range,
'it is a reference to one or more cells in a worksheet
For Each cell In rng '
If cell.Value = ("Desired Value") Then
'This will not work for a number of reasons
'O- As BrakNicku stated, there needs to be 'Set' at the front
'O- This is not remembering the values into the 'PTRange' but
' adding a reference to where those values are. I.e. You are
' not taking note of everybody in the house, but just remembering
' their house address
'O- You do not need to put 'Range()' around 'PTRange' as it is
' already a range
'O- 'Rows(count)' is not valid syntax and does not pass a range into
' the union
'O- 'Union' will put together 2 or more references to ranges, it
' will not put their values in any specific place on a worksheet
PTRange = Application.Union(Range(PTRange), Range(Rows(count)))
End If
count = count + 1
Next cell
I hope the above has been informative and below is an example to a potential solution:-
Option Explicit
Public Sub Sample()
Dim LngRow As Long
Dim Rng As Excel.Range
Dim Rng_Cl As Excel.Range
Dim Rng_PT As Excel.Range
LngRow = 21
'Initialise my Range
Set Rng_PT = Range("E21") '" & LngRow)
'Get the search range
Set Rng = Range("E2:E20")
'Loop through all the cells
For Each Rng_Cl In Rng
If Rng_Cl.Value = ("Duck") Then
'Adds just 5 column and not the whole row
Range(Rng_Cl.Address, ActiveSheet.Cells(Rng_Cl.Row, Rng_Cl.Column + 5).Address).Copy Range("E" & LngRow)
LngRow = LngRow + 1
End If
Next
'It is good practice to remove references once you are done with them
'This can help with memory consumption/speed and also more serious
'memory leaks (and actual stack overflows!) on larger macros
Set Rng = Nothing
Set Rng_PT = Nothing
End Sub

Related

Copy non-contiguous cells and paste in a row retaining number format Excel VBA

I hope someone can help me with this as it's driving me up the wall!
There are 5 non-contiguous cells in a worksheet that I want to copy to the next empty row on another worksheet whilst retaining the number formatting (which varies). I have this so far but am struggling working out how to retain formatting. Can anyone please help? Thanks I anticipation.
`With wsCalc
For bRun = 1 To 4
bData(bRun) = Application.Choose(bRun, .Range("g2"), .Range("b2"), .Range("R2"), .Range("Q14"))
Next bRun
End With
wSResults.Cells(Rows.Count, "a").End(xlUp).Offset(1).Resize(, 4).Value = bData
`
Here's a possible solution, using your hard-coded cell addresses. You will have to set wsCalc and wsResults to their proper worksheets. Slightly more elegant would be to define a "non-contiguous" range on your wsCalc sheet (select the 1st cell, keep Ctrl pressed and select the next one etc, then type a name in the drop-down box just to the left of the formula bar).
Option Explicit
Sub CopyWithFormat()
Dim wsCalc As Worksheet
Set wsCalc = ActiveSheet 'Or whatever your calc sheet is
Dim rngSource As Range
Set rngSource = wsCalc.[G2,B2,R2,Q14]
Dim wsResults As Worksheet
Set wsResults = ActiveSheet 'Or whatever your result sheet is
Dim clDest As Range
Set clDest = wsResults.Cells(Rows.Count, "a").End(xlUp).Offset(1)
Dim cl As Range
For Each cl In rngSource.Cells
clDest.Value = cl.Value
clDest.NumberFormat = cl.NumberFormat
Set clDest = clDest.Offset(1)
Next cl
End Sub
Instead of using .Value, try .Text. It retains formatting. See below.
Gary's Student is right, text is read only, it should be used for the input not the output.
bData(bRun) = Application.Choose(bRun, .Range("g2").Text, .Range("b2").Text, .Range("R2").Text, .Range("Q14").Text)
I also agree with other answer the entire code could be set up more straight forward.

Copying a filtered data from a table set and paste it on the next empty row of another sheet

I tried to copy a set of filtered data from the worksheet "PEP" (Filter: Emetteurs, in column 18) and paste it in the next empty row in the worksheet"Sanctions". I found this text code online and add the section NextRow in order to paste it on the next Empty Row in the "Sanctions" Worksheet. I just started using VBA not long ago, so I tried to use codes online however in this case, I think the problem here is that I cannot define the value of range. I tried to look it up online but no result. Thank you in advance for all your help!
Dim k,j,i As Interger
ActiveWorkbook.Sheets("Sanctions").Select
NextRow=Cells(Range("A"&Rows.Count).End(xlUp).Row+1,1
k=1
With sheets("PEP")
T=.Range("A1:AS"&.Cells(Rows.Count,1).End(xlUp).Row)
.Rows(1).Copy Sheets("Sanctions").Range("NextRow")
End With
For i=2 To UBound(T,1)
If T(i,18)="Emetteurs" Then
k=k+1
For j =1 to UBound(T,2)
T(k,j)=T(i,j)
Next j
End If
Next i
With Sheets("Sanctions")
Application.ScreenUpdating=False
.Range("NextRow").Resiwe(k,UBound(T,2))=T.Offset(1)
.Columns.AutoFit
Application.ScreenUpdating=True
End With
End Sub
And if possible I would also like to find a solution to remove the header once I do the Copy-Paste process.
Read the code's comments and adjust it to fit your needs.
Some things to begin:
Use option explicit so you don't have unexpected behavior with undefined variables
Always indent your code (see www.rubberduckvba.com a free tool that helps you with that)
Try to separate your logic defining variables and the reusing them
EDIT: Some reviews to your code:
As mentioned by #Pᴇʜ you need to declare each of your variables types.
This:
Dim k,j,i As Interger
is the same as declaring:
Dim k As Variant
Dim j As Variant
Dim i As Integer
Side note. You had a typo in Interger
Which is not what you really want, knowing that all of them are going to store numbers
Declare the objects that you're going to work with. For example, you refer to the sheet Sanctions three times in your code:
ActiveWorkbook.Sheets("Sanctions")
This can be set once like this:
Set targetSheet = ThisWorkbook.Worksheets("Sanctions")
And then reused in lines like this:
With Sheets("Sanctions")
or this:
Sheets("Sanctions").Range("NextRow")
by writing this:
With targetSheet
in this way, if you ever need to change it (the person working with your code, or your future you will be really thankful)
Declaring your variables to just letters, makes your code really hard to understand.
Dim j,k,l
is different when you have:
Dim lastRow As Long
Dim lastColumn As Long
etc.
I suggest that you use the key F8 and step through the code, and see the logic behind it. That way you can learn more.
Code:
Public Sub ConditionalRowCopy()
' Declare object variables
Dim sourceSheet As Worksheet
Dim targetSheet As Worksheet
Dim cell As Range
' Declare other variables
Dim sourceLastRow As Long
Dim targetLastRow As Long
' Set a reference to the sheets so you can access them later
Set sourceSheet = ThisWorkbook.Worksheets("PEP")
Set targetSheet = ThisWorkbook.Worksheets("Sanctions")
' Find last row in source sheet based on column "R"
sourceLastRow = sourceSheet.Cells(sourceSheet.Rows.Count, "R").End(xlUp).Row
' Find cell with word "Emetteurs", search in column R)
For Each cell In sourceSheet.Range("R1:R" & sourceLastRow).Cells
' If match
If cell.Value = "Emetteurs" Then
' Find last row in target sheet based on column "A"
targetLastRow = targetSheet.Cells(targetSheet.Rows.Count, "A").End(xlUp).Row
' Copy entire row to next empty row in target sheet
cell.EntireRow.Copy Destination:=targetSheet.Range("A" & targetLastRow).Offset(RowOffset:=1)
End If
Next cell
End Sub
Let me know if it works!

How to reference named range in VBA from an inactive sheet?

I see this question asked and answered but I'm missing something! In order to use Named Ranges in my VBA I must activate the sheet before using the Named Range, otherwise it accesses the "same" range in the active sheet. I have:
made sure that the named range scope is set to Workbook.
fully qualified every range and cell method or property in my code
Here is the code. It works fine if I put back in the worksheet.activate call but otherwise it references a range in the wrong sheet (the active one).
Sub UserForm_Initialize()
' Application.ScreenUpdating = False
Dim r1 As Long, c1 As Long, r2 As Long, c2 As Long, rng1 As Range, rng2 As Range, s As Worksheet, initSheet As Worksheet
Set s = Sheets("NamePlates")
Set rng1 = s.Range("MasterStoreNamePlates")
Set initSheet = Application.ActiveSheet
r1 = rng1.Row
c1 = rng1.Column
r2 = r1 + storeCount - 1
c2 = c1
's.Activate
With s
listBoxStores.RowSource = .Range(.Cells(r1, c1), .Cells(r2, c2)).Address
End With
'initSheet.Activate
' Application.ScreenUpdating = True
End Sub
It's not too demanding to switch to a different sheet for a quick range lookup, but come on... what's the point of naming the ranges and making the name global and all the posts about "make sure to set the name to global" if I have to still go and switch the active sheet?
Thanks in advance!
An update:
Sub UserForm_Initialize()
Application.ScreenUpdating = False
Dim rng1 As Range, rng2 As Range, sInd As Long, initSheet As Worksheet, s As Worksheet
sInd = Sheets("NamePlates").index
Set s = Sheets(sInd)
Set rng1 = s.Range("NamePlates!MasterStoreNamePlates")
Set initSheet = Application.ActiveSheet
listBoxStores.RowSource = rng1.Range(s.Cells(1, 1), s.Cells(storeCount - 1, 1)).Address
Application.ScreenUpdating = True
End Sub
Doesn't provide any different result. It still accesses the active sheet. If I change the active sheet before doing this, it retrieves the right data, otherwise it pulls the range from the active sheet.
I noticed that s.Range("NamePlates!MasterStoreNamePlates") does get me the correct range... but then when I use that range and do something relative to it, I lose the sheet reference. So I think maybe my solution will involve dynamically updating the named range and then this method should work. Currently you can see that I'm using that constant "storeCount" which is not really a very good way to do things. It is just kind of my hack for getting things up and running. All the other suggested methods fail in my case for the same reason; even if they make a good reference to the correct range, I loose that sheet reference when I start doing anything that defines the range I want, which is 12 rows longer than the named range (thus the "storeCount" constant).
If I can't figure out how to do a dynamically changing named range I'll post a new question but I've seen some things about that so I should be able to get that working and I'll post my solution here... or if someone beats me to it I'll accept that answer even though it's a little different from the OP.
ThisWorkbook.Names("NamedRange").RefersToRange
This will return the range object of the named range.
i am adding another answer as the original question was modified significantly. it looks to me that you are over-complicating a matter which is rather simple if you understand it.
Before i explain further, i think your approach with setting the ".RowSource" with the ".address" property of a range is incorrect because the address property does not contain a reference of the parent sheet so it will always refer to the active sheet even if you have gotten that address from a global wb range. see below:
i hope the below would be helpful to you to achieve what you want. No ranges! No activations! keep it simple. i have been working with vba 12 years and i can count in one hand the number of times i have used .activate & .select
I have initialized my form with 3 list boxes (i) static hardcoded range (ii) named range and (iii) dynamic named range.
Private Sub UserForm_Initialize()
Dim addr1 As String
Dim addr2 As String
Dim addr3 As String
addr1 = "LB!$A$1:$A$4"
addr2 = "List.Items" ' named range defined as "=LB!$A$1:$A$3"
addr3 = "List.Items.Dyn" ' dynamic named range defined as "=OFFSET(LB!$A$1,0,0,COUNTA(LB!$A:$A),1)"
Me.testListBox1.RowSource = addr1
Me.testListBox2.RowSource = addr2
Me.testListBox3.RowSource = addr3
Debug.Print Me.testListBox1.RowSource
Debug.Print Me.testListBox2.RowSource
Debug.Print Me.testListBox3.RowSource
End Sub
here is the Debug.Print result in the immediate window
In my user form i have three listboxes populated from the same cells but referenced differently in the .RowSource property. It also works regardless which is the active sheet.
here i am initializing the same form from another worksheet
why do you want to activate the sheets when you could possibly do:
listBoxStores.RowSource = s.Range(s.Cells(r1, c1), s.Cells(r2, c2)).Address
instead of:
With s
listBoxStores.RowSource = .Range(.Cells(r1, c1), .Cells(r2, c2)).Address
End With

Adding a order to master inventory using excel macros

I have two separate excel workbooks. One is an order form, the other is a master inventory file.
I have a column where I input the order amount for each individual items (let's say it spans from cell C2:C130, each row is a different item). My optimal solution is to develop a macro that takes that order and adds it to an existing master inventory to keep track of total orders.
I wrote some code that I thought would work:
Private Sub CommandButton1_Click()
Dim wsCopy As Worksheet
Dim wsDest As Worksheet
Dim lCopyLastRow As Variant
Dim lDestLastRow As Variant
Workbooks.Open "C:\Users\Dave\Desktop\Designs Work\Master_Inventory.xlsm"
Set wsCopy = Workbooks("Production file_Dave Edits").Worksheets("Order")
Set wsDest = Workbooks("Master_Inventory").Worksheets("Sheet1")
Set lCopyLastRow = wsCopy.Range("E2:E130")
Set lDestLastRow = wsDest.Range("E2:E130")
lDestLastRow.Value = lDestLastRow.Value + lCopyLastRow.Value
End Sub
when I run this code, I get a mismatch error (type 13?).
So I went into the watch window to see the type of each expression and the combined right side of the equation is a "variant/integer" type, whereas each individual expression is a "variant/variant" type. Moreover, when I run the code and call only one cell instead of a matrix, the code works; it doesn't run when multiple cells are called.
Can anyone help? Or have a more elegant code? Thank you
Set lCopyLastRow = wsCopy.Range("E2:E130")
Set lDestLastRow = wsDest.Range("E2:E130")
This makes both variables Variant/Range, because the Set keyword says the right-hand side of the assignment operator is yielding an object reference: the two variables might as well be declared As Range.
Now, the Value of a Range object that refers to multiple cells, is a 2D Variant array.
lDestLastRow.Value = lDestLastRow.Value + lCopyLastRow.Value
That's where you're getting the type mismatch error, because you can't use the + operator with array operands.
when I run the code and call only one cell instead of a matrix, the code works
That's because a single-cell Range.Value returns that numeric value, and + will work with that - although, if the cell contains an error value (e.g. #REF! or #VALUE!), you'll still get a type mismatch error, because a Variant/Error can't be an operand.
I wish I could help beyond that, but I have no idea what this + intends to be doing.
As for a more elegant solution, I'd recommend indenting the procedure body, moving declarations closer to their assignment, and keeping a reference to the destination "inventory" workbook:
Private Sub CommandButton1_Click()
Dim sourceSheet As Worksheet
Set sourceSheet = Workbooks("Production file_Dave Edits").Worksheets("Order")
Dim inventoryBook As Workbook
Set inventoryBook = Workbooks.Open("C:\Users\Dave\Desktop\Designs Work\Master_Inventory.xlsm")
Dim destSheet As Worksheet
Set destSheet = inventoryBook.Worksheets("Sheet1")
Dim sourceRange As Range
Set sourceRange = sourceSheet.Range("E2:E130")
Dim destRange As Range
Set destRange = destSheet.Range("E2:E130")
'todo: figure out intent
'lDestLastRow.Value = lDestLastRow.Value + lCopyLastRow.Value
End Sub
If that + intends to add everything up in both ranges, you could use Application.WorksheetFunction.Sum(sourceRange) + Application.WorksheetFunction.Sum(destRange), although I doubt that's really want you're looking for.

Unable to read object properties of Set Range variables

I am developing a workbook with vba; in it are worksheets that can contain a varying amount of named "CheckCells" which act as "boolean" dropdowns ("Yes"/"No") to manipulate other cells within the same worksheet.
I'm trying to develop a Public Function macro which can be called within a formula of a cell. Its purpose is to "collapse" all of these CheckCells into a single Long-type output -- the output will equal the column index from which I read off of special "archive" worksheets within the workbook; the "archive" sheets will act as permanent "memory" for certain values, as they are critical to the workbook function but would be lost upon workbook open/close.
The following is a shortened version of the code I need help with. As indicated by commented Debug.Print lines, it appears that any way I try to Dim a Range-type variable, then Set it to equal a range of cells, most attempts at reading using that variable's object properties throw errors results such as "Cannot get property...", "Object-defined error...", or "Application-defined error...". "VersionCheckCell" is indeed a named cell of the ActiveSheet here.
Public Function CollapseRun() As Long
Dim ActSht As Worksheet
Set ActSht = ThisWorkbook.ActiveSheet
'Variables to get the range of cells to loop through when finding VersionCount.
Dim intVisRows As Long: intVisRows = 1
Dim intVisCols As Long: intVisCols = 1
'[Code to evaluate inVisCols and intVisRows omitted to shorten post...]
Dim EndCell As Range
Set EndCell = ActSht.Cells(intVisRows, intVisCols)
'Debug.Print [VersionCheckCell].Name.Name 'This works. If explicitly giving valid ranges, _
I can read its properties like normal.
'Debug.Print EndCell.Name.Name 'This fails. When I define a _
Range variable, then Set it as some cells, I can't read its object properties.
Dim VisibleRange As Range
Set VisibleRange = ActSht.Range(ActSht.Cells(1, 1), EndCell) 'Cell Range to
loop through.
Dim intCzCells As Integer: intCzCells = 0
Dim Cell As Range
Dim arrCz() As Variant
'This is the Application-defined error-throwing loop.
For Each Cell In VisibleRange
If InStr(Cell.Name.Name, "CheckCell") <> 0 Then 'Checks cell's name for "CheckCell".
intCzCells = intCzCells + 1
arrCz(intCzCells) = Cell.Address 'Storing addresses to later check values.
End If
Next Cell
'[More code after this loop to determine CollapseRun value...]
I did research before asking and found many other questions with labels such as "Application-defined error when doing ...", etc. The problems/solutions of many of these stemmed from the QA improperly Setting the variable which was throwing errors when being read. I've stared and tinkered with this code for several hours and cannot figure out how I might be improperly setting the VisibleRange or the EndCell variables, so I'm inclined to believe something else is wrong, but I'm hoping someone here can nudge me in the right direction.
I'm on Windows 10 using Excel 2016.
(Edit: Insert missing portion of code where intCzCells was initialized as 0.
(Edit 2: Fixed unnecessary range evaluation as pointed out in a comment.)
The main issue, as pointed out by John Coleman in comments, was that .Name.Name would throw errors if the range wasn't a named cell.
One way to resolve this issue is to use workaround error handling within the loop (I also fixed the code to actually add the addresses to the array properly).
For Each Cell In VisibleRange
On Error GoTo NoName
If InStr(Cell.Name.Name, "CheckCell") <> 0 Then
intCzCells = intCzCells + 1
ReDim Preserve arrCz(intCzCells - 1) 'Array indexed from 0.
arrCz(intCzCells - 1) = Cell.Address
End If
NoName:
Resume NextIteration
NextIteration:
Next Cell

Resources