I have been busy working on a number of dynamic excel workbooks learning on the go through forums and content like this site, but as I am coming to the final parts that will round off my workflow I am stumped by what I assumed would be a fairly simple VBA code.
Not wanting to muddy the water with the code I currently have, I will instead explain clearly the problem I am trying to solve, and the ways I have tried to approach this. Note that I am very much a novice, and would appreciate being pointed in the direction of some code that I am able to copy and amend to suit.
Problem
I have two sheets;
"Purchase_Orders" - is a master list of total value purchase orders with information associated with the total value.
"Purchase_Ledger" - is a full list of payment transactions that include a mixture of payments which are project and non-project related.
As a project will have multiple purchase order numbers, and purchase orders have multiple payments, I am looking to lock the sheet down so that the user selects the purchase order number they are making a payment against, and the columns for project data are automatically pulled across from "Purchase_Orders" that are associated with the purchase order.
The data is structured as follows:
"Purchase_Orders"
(A,7-4000) Purchase Order number
(B,7-4000) Project Number
(C,7-4000) Project Name
"Purchase_Ledger"
(A,7-10000) Payment Reference
(B,7-10000) Purchase Order number - purchase order is selected from a drop-down list of numbers validated from the list on "Purchase_Orders"
(C,7-10000) Project Number - to be copied across
(D,7-10000) Project Name - to be copied across
My Attempts
I have already spent so much time working on this from different angles. As there are likely to be some purchases that are required that do not necessarily require a purchase order number, I want to keep the project number column open and free from formulae.
Currently, I have an index and match formulae in VBA which only works when run manually and copies the whole columns across. Ideally, I am looking for only the relevant cells to update when a purchase order is selected. I am working on code to trigger the macro when the drop-down is selected, but this is all very clumsy and not what I am seeking to achieve.
I have also tried using vlookup, but again this would only work with a trigger and not dynamic.
Hopefully, this is a sizeable challenge for some to flex their grey matter. If you need more info let me know.
Thanks!
Jon
Current code:
Sub Copy_Purchase_Orders()
Dim k As Integer
For k = 7 To 10000
Cells(k, 6).Value = WorksheetFunction.Index(Range("B7:B4000"), WorksheetFunction.Match(Cells(k, 5).Value, Range("A7:A4000"), 0))
Cells(k, 7).Value = WorksheetFunction.Index(Range("C7:C4000"), WorksheetFunction.Match(Cells(k, 5).Value, Range("A7:A4000"), 0))
Next k
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Application.Intersect(Range("E7:E1000"), Range(Target.Address)) Is Nothing Then
Call Copy_Purchase_Orders
End If
End Sub
So your current code is called whenever a selection happens in column E and then cycling through all rows from 7 to 10000 and populating it with the formula?
You could change the method to accept the cell / target / range / address and then only cycle through each of its rows (instead of 7 to 10000)
Something like this (untested because I didn't have data sample and short on time):
Sub Copy_Purchase_Orders(target As Range)
Cells(target.Row, 6).Value = WorksheetFunction.Index(Range("B7:B4000"), WorksheetFunction.Match(Cells(target.Row, 5).Value, Range("A7:A4000"), 0))
Cells(target.Row, 7).Value = WorksheetFunction.Index(Range("C7:C4000"), WorksheetFunction.Match(Cells(target.Row, 5).Value, Range("A7:A4000"), 0))
End Sub
Private Sub Worksheet_Change(ByVal target As Range)
If Not Application.Intersect(Range("E7:E1000"), Range(target.Address)) Is Nothing Then
Call Copy_Purchase_Orders(target)
End If
End Sub
edit
To reference a particular worksheet when using Range objects, you should "qualify" them with the worksheet you're targeting (it's always safer to do this, you shouldn't really use Range or Cell objects alone:
So if want to move sheet 2 data to sheet 1 data, Instead of:
Range("A1").Value = Range("A1").Value '(VBA doesn't know which Range you mean)
You write:
Worksheets("Sheet 1").Range("A1").Value = Worksheets("Sheet 2").Range("A1").Value
So, you just need to qualify the ranges in your code, I think like so:
Cells(target.Row, 6).Value = WorksheetFunction.Index(Worksheets("Purchase_Orders").Range("B7:B4000"), WorksheetFunction.Match(Cells(target.Row, 5).Value, Worksheets("Purchase_Orders").Range("A7:A4000"), 0))
I think for the purposes of your code at the minute, you can leave it there. If you decided to build more on these existing routines, you should really qualify your Cell objects too, however it would end up very wordy:
Worksheets("Purchase_Ledger").Cells(target.Row, 6).Value = WorksheetFunction.Index(Worksheets("Purchase_Orders").Range("B7:B4000"), WorksheetFunction.Match(Worksheets("Purchase_Ledger").Cells(target.Row, 5).Value, Worksheets("Purchase_Orders").Range("A7:A4000"), 0))
To cut down on mess, you can declare worksheets as variables and set them as particular sheets:
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = Worksheets("Purhcase_Orders")
Set ws2 = Worksheets("Purchase_Ledger")
Then use them in place of writing their full object name:
ws2.Cells(target.Row, 6).Value = WorksheetFunction.Index(ws1.Range("B7:B4000"), WorksheetFunction.Match(ws2.Cells(target.Row, 5).Value, ws1.Range("A7:A4000"), 0))
There are even more shortcuts and declarations you can use to make code more succinct, safer, reusable and scalable, if you choose to really dive into VBA coding. Ranges can be variables, using sheet codenames, using With blocks, in-line evaluate - all things that could be employed to your code so far.
Related
My problem is how to populate the cmbSelProp ComboBox with propIDs from a workbook scoped Named Range called PropLocs. The columns of interest Column "A" which contains the list of propIDs and Column "K" which contains the ActiveStatus of each propID. I also have Named Ranges for both columns of interest, propIDs and actStatus respectively. The actStatus, Column "K", portion of the range are set to True/False when the properties are added and subsequently initialized and annual worksheets are automatically generated.
I only want cmbRptPrpID populated with propIDs having an actStatus = True.
I have spent many hours over the past few weeks going blind doing Web Searches, looking at links, and trying suggestions from the various links without success. I,m as "Lost as a blind man in a snow storm!"
There are broader issues associated with the problem I am dealing with and understanding how to solve, and understand, the issue at hand will help me in the future development of my application.
UPDATE
the 2 mentioned additional ranges are in columns "A" and "K"
Update 2
Business Logic
The application I am developing utilizes a multipage object and there are pairs of dynamic comboboxes, cmbSelProp and cmbYears for example, used to select the active worksheet and enter Monthly expenses, view/generate Reports, etc. cbmSelPropselects the property ID, and the cbmSplProp_Change() event configures cmbYears list values based on the variable wsStartYr in column "K" and wbCurYear which is the Current Calendar Year. the annual worksheets have a Worksheet TabName of propId_Year and are selected using a variable wsA = pID & "_" & wsYr. I do not want propIDs with an Inactive status to appear as a part of the cmbSelProp list values.
This is a link to My First Question here on StakOverflow which is related to this question.
I really need some help figuring this out!
Thanks in advance for your assistance.
here is the code I have been trying to work with.
selectedRow = cmbSelProp.ListIndex + 3
For Each cPart In wsCntrl.Range(Range("propIDs"), Range("A" & Rows.Count).End(xlUp))
pAct = wsCntrl.Cells(selectedRow, "K").Value
With Me.cmbSelProp
If pAct = "True" And cPart.Value <> "" Then cmbSelProp.AddItem cPart.Value
End With
Next cPart
There are a number of issues in your code. Rather than breaking down all the errors/issues, I'll show you how I would do it.
From your previous Q, I take it that your Combo Box is on a Userform.
I've created a Userform with a combo box and a button to load it. You can change how you call the load function to suit your needs.
The UserForm code
Option Explicit
Private Sub btnTest_Click()
Load_cmbSelProp
End Sub
Private Sub Load_cmbSelProp()
Dim propIDs As Range
Dim actStatus As Range
Dim rw As Long
Set propIDs = ThisWorkbook.Names("propIDs").RefersToRange
Set actStatus = ThisWorkbook.Names("actStatus").RefersToRange
Me.cmbSelProp.Clear
For rw = 1 To propIDs.Count
If propIDs.Cells(rw, 1).Value2 <> vbNullString Then
If actStatus.Cells(rw, 1).Value2 = True Then
Me.cmbSelProp.AddItem propIDs.Cells(rw, 1).Value2
End If
End If
Next
End Sub
The Named ranges
The result
At my job I have automated a very manual task and to the point they have wanted to expand it to the other department where they work a little differently. So the goal of this is I want to be able to filter column "A" and then filter another column based off of phrases that I already have in place as well. The data in column "A" I would have a source in another sheet, but it would have around potentially 200-400 possibilities to look for. After it filters column A I then want it go to column "AG" and then do another loop filtering based off of provided key phrases that the analyst would select based on a data validation. Once it filters those two criteria I then have the codes in place to generate the spreadsheets for the analyst. The code below is an example of the first block, I have 4 other codes that are pretty much the same they just generate different templates, I had to do multiple codes cause I didn't know how to do a loop based off of a source.
Sub Generate_Consolidated_Update_Reports()
Dim EDI As String
EDI = Environ("USERPROFILE") & "\desktop\foldername\foldername2\foldername3\filename " &
Format(Now(), "MM.DD.YY")
Workbooks("Master(where the filtering happens)").Activate
'The next line of code I am just doing the second filtering which is based off of a data validation
'I created, essentially the analyst would just select a key phrase and then that would
'prompt the code to generate the template, I haven't figured out how to do it based off of
'the source that's why I just have the specific name in the key range
'The first step to the code would need to be looking for anything in column "A", but like
'I said that could be anywhere from 200-400 possibilities. I have access to it, though and have
'it listed in "Sheet2" along with the phrases in column "AG" as well.
ActiveWorkbook.Worksheets("Master").Sort.SortFields.Add Key:=Range("AG:AG" _
), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With Selection
.autofilter Field:=33, Criteria1:="Send to EDI Team - Account Level"
.Offset(1, 0).Select
End With
'The next line of code will be seeing if it is empty, I realized that it would work once,
'but if there ever was data the next line of looking for empty would always
'just filter anyways and keep creating the template even though there was no data
'so I did this route where if it saw it was empty I called another code, would be awesome
'if I could figure that out too!
'This then goes on repeat down the line, it's 5 different codes, so it isn't clean. :/
'so I only did one so you wouldn't see a ton of fluff!
Dim Lr As Long
Lr = Range("A" & Rows.Count).End(xlUp).Row
If Lr > 1 Then
Workbooks.Add
ActiveWorkbook.SaveAs EDI & ".XLS"
Sheets("Sheet1").Select
Sheets("Sheet1").Name = "EDI Account Update"
'Redacted code, just fluff on creating the template for the analyst
'Next line is just doing the code to show all the data again
'Then within that if statement to then call another sub that is essentially the same process
'If the code doesn't find it it goes to else where it then just calls the other sub
If ActiveSheet.AutoFilterMode Then ActiveSheet.ShowAllData
Range("A1").Select
Call Create_EDI_Update_GroupLevel
Else
ActiveSheet.ShowAllData
Range("A1").Select
Call Create_EDI_Update_GroupLevel
End If
End Sub
The reason I have to also call other subs is because each criteria they select will generate a completely different template based on company policies and such.
Really sorry if this isn't clear, I am still learning coding and just having a hard time trying to explain exactly what I needed. Thank you so much for your time.
I wanted to add a comment but not enough reputation yet.
One question I have is: Are you filtering on one phrase among 200-400 or can it be multiple?
I can't be sure, however an Advanced Filter might help you in this case. It is not possible for me to go into all the details. There is a very good tutorial on Youtube about this (where I learned myself): VBA Advanced Filter - The FASTEST way to Copy and Filter Data
You can also use "Worksheet_Change" event to fill in the filter. Couple it with:
If Not Application.Intersect(Target,rngRange) is Nothing Then
' Your code here
' Where rngRange is a Range object that you want the change event to catch
End If
Another note is, you can use (considering data starts at "A1") Range("A1").CurrentRegion if you don't have any completely empty rows within your data instead of .End(xlUp). Actually you can use CurrentRegion in any cell that is inside the data range. Check out the behaviour by pressing CTRL + * after selecting a cell.
Dim rngData as Range
Set rngData = ws.Range("A1").CurrentRegion
' ws can be a worksheet object you can set alike range above or worksheet codename
' or Worksheet("SheetName")
' Then rngData.Row will give the top row of rngData
' rngData.Column will give you you the leftmost column
' rngData.Rows.Count will give the number of rows
' rngData.Columns.Count will give the number of columns
' rngData.resize() and rngData.Offset() helps a lot too, can be used in same line
' i.e., rngData.offset(2,0).resize(1,1) : move the range 2 rows down, resize to 1 row,1 column
' Do whatever with rngData
' After you are done clean up, doesn't matter most of the time in VBA but this is better
' from what I have read/learned from others
Set rngData = Nothing
This may not be the exact answer you are looking for, but may get you started. The video is 20 minutes long, see if it can be used in your case.
Edit 1: To further clarify
rngData part is more a general approach. You haven't said anything about being able to use Advanced Filtering but here how I would do it.
Assume, we have 3 sheets. wsData, wsFilter, wsReport.
wsData would be the sheet where data is entered.
wsFilter would only hold the filter criteria.
wsReport would be the sheet that you will export.
wsData, as an example assume row 1 is the header row.
wsFilter would be only 2 columns (with headers corresponding to A and AG), corresponding to Column A and AG.
On wsReport, you only have to clear contents and enter the column headers from wsData that you would want to appear on the customized report.
You will need 3 ranges, 1 for each worksheet. i.e., rngData, rngFilter, rngReport
The code to creating the report is easy as:
rngData.AdvancedFilter xlFilterCopy, rngFilter, rngReport
(I won't get into how to decide on the ranges as the video does it better than I would be able to).
Then next step should be: How to enter the filter criteria?
I have 7 worksheets which do exactly what I want. I am now being asked for a filter to show specific years. Done. However to look at a year of trend data, I have to manually filter each sheet.
I wouldn't mind going the extra mile, and if it's possible, have a filter in one of these sheets that organises the year in all the other sheets.
I have=YEAR(O9:O29148) on my largest sheet. A8:O8 and everything above is exactly the same on each sheet, every sheet has the same type of data in the same column. The only thing that does change is the unique data itself.
What I want is to have a Year filter (2000-2018) on my dashboard, which will then filter all the worksheets to show the same year, or all data if required.
Is this even possible?
(I do not understand VBA code, but I am capable of inserting it into VBA editor and then running said macro).
Any help would be greatly appreciated, thank you!
Not really knowing a lot about the way your data is set up, I build the following, with this code on the worksheet_change event of the dashboard sheet, where I have E6 controlling the year. I have 3 other sheets with data in column A with year numbers, you can use this as a base. You will need to experiment with your column, on the filter, number most likely.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wsWorksheet As Excel.Worksheet
If Target.Cells(1, 1).Address(False, False) = "E6" and Target.Cells.Count=1 Then
For Each wsWorksheet In ThisWorkbook.Worksheets
With wsWorksheet
If .Name <> Target.Worksheet.Name Then
If .UsedRange.AutoFilter Then
.UsedRange.AutoFilter 1, Target.Value
End If
End If
End With
Next wsWorksheet
End If
End Sub
Public Sub Filter_Sheets()
Dim i As Long
Dim comboBox As ControlFormat
With ThisWorkbook
Set comboBox = .Worksheets(9).Shapes("Drop Down 229").ControlFormat
For i = 1 To Worksheets.Count
.Worksheets(i).UsedRange.AutoFilter Field:=15, Criteria1:=comboBox.List(comboBox.ListIndex)
Next
End With
End Sub
This is the best fit I have managed to discover. I still get an error (AutoFilter method of Range class failed). However this does work. I am now using a combobox to change the auto filter on all 7 sheets as needed. In order to go back to select all, having "<>" in a cell the dropdown references, works to select all the data again.
I have a master list of products and stock counts (see image below), but I want for when a product is selected (on another sheet) for the stock count to be subtracted by 1.
Example data set
What is the best way of doing this? (I tired a bit of VBA but didn't get too far)
Edit:
Here is how my mind would picture it working:
How I picture how it would work
Basic VBA Example.
The Worksheet_Change event needs to be on the sheet that contains the dropdown.
Just check to see if the address of the changed cell is the address of your dropdown. You can use a named range or you can just use a static address as I did below.
I gave the list a named range called Items which we can reference.
Please note that on a separate sheet, you would need to use Sheets("SheetName").Range("Items")
I also used a simple For Each loop to check the values in the list - it may be better if it gets large enough to use a dictionary, variant array, or Find.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$H$4" Then
Dim c
For Each c In Range("Items")
If c = Target.Value Then
c.Offset(0, 1).Value = c.Offset(0, 1).Value - 1
Exit For
End If
Next c
End If
End Sub
I have a workbook that I'm trying to figure out how to do what I want. Macros are not an issue and Im certain I will have to use vba for my needs but dont know enough to put a dent in what i want to do.
The Basic premise is as follows. I have a two sheet workbook, Sheet 1 contains the current date and two fields one for #of Calls and the other for # of completes. Sheet 2 has a series of dates (for the month) and the same 2 fields form sheet 1 for all the dates. What I want to happen is, depending on the date on sheet 1 then sheet 2 will autofill the proper fields for that date. Leaving all other fields alone. I cannot get it to where it will leave the rest of the spreedsheet alone (not blank them out or put 0) and only populate the correct date.
I have uploaded a sample spreed sheet here. If anyone could provide me with any direction I would be greatly appreciative. If anything is unclear then please ask for clarification. Thanks in advanced.
STP
You may run this macro:
Option Explicit
Sub r()
Dim c as Range
For Each c In Worksheets("Sheet2").Range("A2:A999")
If c.Value = Worksheets("Sheet1").Range("A2").Value Then
c.Offset(0, 1).Value = Worksheets("Sheet1").Range("b2").Value
c.Offset(0, 2).Value = Worksheets("Sheet1").Range("c2").Value
End If
Next c
End Sub