userform using variable of a commandbutton - vba

I have a problem that I can not solve ... my problem is this:
I need to create a User Form that performs data search within 4 sheets, this data is the same only changing from year to year in the spreadsheets, but when I try to link my CommandBotton and use the variable in the error combo box ...
The business rule is as follows:
The User enters the employee's enrollment in this automatically pulls the data to the fields of the User Form, in case he wanted to change the worksheet he used the combo box to change between those worksheets and executing the same search but in different worksheets.
Public plan As Worksheet
Sub ComboBox1_Change()
Sheets(ComboBox1.ListIndex + 1).Activate
End Sub
Sub UserForm_Initialize()
For Each plan In ActiveWorkbook.Worksheets
ComboBox1.AddItem plan.Name
Next plan
End Sub
Sub bnt1_Click()
With ThisWorkbook.Sheets(plan).Range("A:A")
Set c = .Find(textCp.Value, LookIn:=xlValues, lookat:=xlPart)
If Not c Is Nothing Then
c.Activate
textCp.Value = c.Value
textName.Value = c.Offset(0, 1).Value
textAd.Value = c.Offset(0, 2).Value
text60.Value = c.Offset(0, 65).Value
text60_20.Value = c.Offset(0, 66).Value
text100.Value = c.Offset(0, 67).Value
text100_20.Value = c.Offset(0, 68).Value
textAdc.Value = c.Offset(0, 69).Value
textAdcT.Value = c.Offset(0, 70).Value
End If
End With
End Sub
Sub btnSair_Click()
Unload FormPes
End Sub

Simply use With plan.Range("A:A").
Here is how I would write the code. I fell like With c.EntireRow would make it easier to identify which columns that you are referring to, as opposed to c.Offset(0, 65).Value.
Sub bnt1_Click()
With plan.Range("A:A")
Set c = .Find(textCp.Value, LookIn:=xlValues, lookat:=xlPart)
If Not c Is Nothing Then
With c.EntireRow
textCp.Value = .Value
textName.Value = .Range("B1").Value
textAd.Value = .Range("C1").Value
text60.Value = .Range("BN1").Value
text60_20.Value = .Range("BO1").Value
text100.Value = .Range("BP1").Value
text100_20.Value = .Range("BQ1").Value
textAdc.Value = .Range("BR1").Value
textAdcT.Value = .Range("BS1").Value
End With
End If
End With
End Sub

Related

Changing Font type in dynamic ranges over multiple worksheets

I'm trying to create a loop to change the font type and font color based on text in the cells in 3 worksheets, each with a named dynamic range. I cannot select all of the cells on the worksheets because I have a legend in cells above the range.
I have successfully formatted each range separately, but I was wondering if there is a more efficient way. I understand that Range will not work on more than one worksheet. I tried to use Collection and Array with the named ranges. I clearly don't understand how to use these, because neither worked.
I've been attempting to figure this out for a week. I've read so many posts, but most of them are attempting a function over a defined range in the worksheets. I'm very new to VBA (all coding) and this is the closest that I have come.
This is what has worked.
Sub Macro3()
Dim daily As Worksheet, mon As Worksheet, per As Worksheet
Dim ws As Worksheet, cell As Range
Dim d1 As Range, m1 As Range, p1 As Range
Set daily = Sheets("Daily")
Set mon = Sheets("Monthly")
Set per = Sheets("Personnel")
Set d1 = daily.Range(("A7"), daily.Range("A7").End(xlUp) _
.Offset(-1, 46))
Set m1 = mon.Range("A6:Y6")
Set p1 = per.Range(("A4"), per.Range("A4").End(xlUp).Offset(1, 20))
With d1
Cells.Replace What:="", Replacement:="T"
Cells.Replace What:="Incomplete", Replacement:="T"
Cells.Replace What:="Complete", Replacement:="R"
Cells.Replace What:="Not Applicable", Replacement:="x"
End With
d1.HorizontalAlignment = xlCenter
For Each cell In d1
If cell.Value = "T" Then
cell.Font.Name = "Wingdings 2"
ElseIf cell.Value = "R" Then
cell.Font.Name = "Wingdings 2"
ElseIf cell.Value = "x" Then
cell.Font.Name = "Webdings"
ElseIf cell.Value = "v" Then
cell.Font.Name = "Wingdings"
End If
Next
With d1
.Borders(xlInsideVertical).Weight = xlThin
.Borders(xlInsideHorizontal).Weight = xlThin
.Borders(xlEdgeLeft).Weight = xlMedium
.Borders(xlEdgeTop).Weight = xlMedium
.Borders(xlEdgeBottom).Weight = xlMedium
.Borders(xlEdgeRight).Weight = xlMedium
End With
' this is repeated for m1 and then p1
End Sub
This did not
Set dta_all = Array(Sheets("Daily").daily.Range(("A7"), _
daily.Range("A7").End(xlUp).Offset(-1, 46)), _
Sheets("Monthly").Range("A6:Y6"), _
Sheets("Personnel").Range(("A4"), _
per.Range("A4").End(xlUp).Offset(1, 20)))
For Each ws In ThisWorkbook.Worksheets
For Each cell In dta_all
If cell.Text = "Incomplete" Then
cell.Value = "T"
cell.Font.Name = "Wingdings 2"
cell.Font.Bold = True
cell.Font.Color = vbRed
End If
Next
Next
I get a 438 error- Property or method not supported. I would greatly appreciate your help.
If you look at the common/repeated parts of your code:
With d1
.Cells.Replace What:="", Replacement:="T"
'etc
End With
d1.HorizontalAlignment = xlCenter
For Each cell In d1
'etc
Next
With d1
.Borders(xlInsideVertical).Weight = xlThin
'etc
End With
What you can do is to create a separate sub containing only those parts, which takes a Range as an argument:
Sub ApplyFormat(rng As Range)
With rng
.Cells.Replace What:="", Replacement:="T"
'etc
End With
rng.HorizontalAlignment = xlCenter
For Each cell In rng.Cells
'etc
Next
With rng
.Borders(xlInsideVertical).Weight = xlThin
'etc
End With
End sub
...and then call if from your main code:
ApplyFormat d1
ApplyFormat m1
ApplyFormat p1
Whenever you find yourself writing the same set of lines more than once, it may be a good candidate for factoring out into a separate sub: identify the variable part(s) and make them parameters in the Sub or Function.
You can make an array of ranges (i`ve never tried before, but seems a neat option to keep in mind). Reusing your code, and also as Tim suggested, I've made a sample how to do it, please see below:
Option Explicit
Sub Macro3()
Dim wb As Workbook: Set wb = ActiveWorkbook
Dim daily As Worksheet, mon As Worksheet, per As Worksheet
Set daily = wb.Sheets("Daily")
Set mon = wb.Sheets("Monthly")
Set per = wb.Sheets("Personnel")
'Take the ranges into an array of ranges
Dim arrRanges(1 To 3) As Range 'add more as needed
'Set each element of the array as you would have with normal variables
Set arrRanges(1) = daily.Range(("A7"), daily.Range("A7").End(xlUp).Offset(-1, 46)) 'd1
Set arrRanges(2) = mon.Range("A6:Y6") 'm1
Set arrRanges(3) = per.Range(("A4"), per.Range("A4").End(xlUp).Offset(1, 20)) 'p1
Dim R As Long, C As Long, X As Long
'Now you can loop through
For X = LBound(arrRanges) To UBound(arrRanges) 'For each of the ranges
For R = 2 To arrRanges(X).Rows.Count 'For each row in each range - except headers
For C = 1 To arrRanges(X).Columns.Count 'For each column in each range
'Debug.Print arrRanges(X).Cells(R, C).Address 'for debuging purposes
With arrRanges(X)
.Cells(R, C).Value = setReplacements(.Cells(R, C).Value)
Call setFont(.Cells(R, C))
End With
Next C
Next R
With arrRanges(X).Offset(1, 0)
.Resize(.Rows.Count - 1).HorizontalAlignment = xlCenter 'align everything except headers
Call setBorders(.Resize(.Rows.Count - 1)) 'set borders to everything except headers
End With
Next X
End Sub
Function setReplacements(str As String)
'Set the replacements here
Select Case str
Case "", "Incomplete"
setReplacements = "T"
Case "Complete"
setReplacements = "R"
Case "Not Applicable"
setReplacements = "x"
Case Else
'do something here
setReplacements = "T" 'assume incomplete for any other value?
End Select
End Function
Sub setFont(rng As Range)
'Set your other formatting here
Select Case rng.Value
Case "T", "R"
rng.Font.Name = "Wingdings 2"
Case "x"
rng.Font.Name = "Webdings"
Case "v"
rng.Font.Name = "Wingdings"
End Select
End Sub
Sub setBorders(rng As Range)
'Set your borders here
With rng
.Borders(xlInsideVertical).Weight = xlThin
.Borders(xlInsideHorizontal).Weight = xlThin
.Borders(xlEdgeLeft).Weight = xlMedium
.Borders(xlEdgeTop).Weight = xlMedium
.Borders(xlEdgeBottom).Weight = xlMedium
.Borders(xlEdgeRight).Weight = xlMedium
End With
End Sub
One thing to keep in mind... looping over the sheet is never a great idea, especially if you have a large number of rows. Unfortunately when it comes to formatting is not a lot you can do, but that. For general data however, is best to load your data in an array, do your transforms, then spit it out again... the less you interact with the sheets, the faster it runs.

VBA macro run-time too long

I have written several subs which are then called from a main sub. Individual subs run very quickly, most are instantaneous (the DoFind sub takes a few seconds to run due to the large amounts of data in the table) however when I run the main sub it takes up to a minute to execute. Any ideas/tips on why this is the case?
Note, I haven't had much experience with VBA (all has been learnt in the past week). There are other macros used, but they are not shown since even the test sub takes approximately 1 minute
Sub DoFind()
Dim i As Long
i = 1
Do While Sheets("Temp").Cells(i, "A").Value <> Empty
Dim BearingArray(6) As String
BearingArray(0) = Sheets("Temp").Cells(i, "A").Value
BearingArray(1) = Sheets("Temp").Cells(i, "B").Value
BearingArray(2) = Sheets("Temp").Cells(i, "C").Value
BearingArray(3) = Sheets("Temp").Cells(i, "D").Value
BearingArray(4) = Sheets("Temp").Cells(i, "E").Value
BearingArray(5) = Sheets("Temp").Cells(i, "F").Value
BearingArray(6) = Sheets("Temp").Cells(i, "G").Value
With Sheets("Calculations")
.Cells(17, "K").Value = BearingArray(0)
.Cells(19, "O").Value = BearingArray(1)
.Cells(20, "O").Value = BearingArray(2)
.Cells(23, "O").Value = BearingArray(3)
.Cells(22, "O").Value = BearingArray(4)
.Cells(26, "O").Value = BearingArray(5)
.Cells(17, "L").Value = BearingArray(6)
End With
i = i + 1
If Sheets("Calculations").Cells(17, "M").Value = "PASS" Then
Exit Do
Else
End If
Loop
If Sheets("Temp").Cells(i, "A").Value = Empty Then
MsgBox "No available bearing."
End If
End Sub
Sub Create_Sheet_Temp()
ThisWorkbook.Sheets.Add
ActiveSheet.Name = "Temp"
' This creates a new worksheet called "Temp"
End Sub
Sub Copy_Paste()
Dim NewTable As ListObject
Sheets("Calculations").Activate
Set NewTable = Sheets("Calculations").ListObjects("Full_Bearings_List")
NewTable.Range.SpecialCells(xlCellTypeVisible).Select
NewTable.DataBodyRange.SpecialCells(xlCellTypeVisible).Copy
Sheets("Temp").Range("A1").PasteSpecial xlPasteAll
Application.CutCopyMode = False
'This sub copies all visible cells from a filtered table and pastes them to the new sheet called "Temp"
End Sub
Sub test()
Create_Sheet_Temp
Copy_Paste
DoFind
End Sub
You can speed up your code by storing the worksheets in variables (ahead of the loop).
Dim TempWS as Worksheet
Dim CalcWS as Worksheet
set tempws= Sheets("Temp")
set CalcWS=Sheets("Calculations")
Also declare the array outside of the loop. Also Id recommend to use numeric column index.
Sheets("Temp").Cells(i, "G").Value
to
TempWS.Cells(i, 7).Value
Comparing against Empty is not always the best choice, try
... <> ""
EDIT:
For the Copy try t use the destination parameter of the Copy method. Example from the help:
Worksheets("Sheet1").Range("A1:D4").Copy _
destination:=Worksheets("Sheet2").Range("E5")

Copy data cell down or up

I am a noob in vba. However, I would like to implement the following use case to make my cooperate life a lot of easier.
I have the following data:
I would like to copy the first row down until I hit a filled field, the second row also down until I hit a filled field, the third row up and down and the 4th row up.
This is how I would like to have my result sheet should look like.
Any recommendation how to implement this use case in vba?
I appreciate your replies!
can you please try this one? This routine is assuming column G as main start point and checks if column A,B,C,D is empty or not in the same row and fill it up accordingly.
Sub ASD()
Dim lastRow As Long
lastRow = Range("G" & Rows.Count).End(xlUp).Row
For Each c In Range("G:G")
If c.Value <> "" Then
If c.Offset(0, -3).Value = "" Then
c.Offset(0, -3).Value = c.Offset(0, -3).End(xlDown).Value
End If
If c.Offset(0, -4).Value = "" Then
c.Offset(0, -4).Value = c.Offset(0, -4).End(xlUp).Value
End If
If c.Offset(0, -5).Value = "" Then
c.Offset(0, -5).Value = c.Offset(0, -5).End(xlUp).Value
End If
If c.Offset(0, -6).Value = "" Then
c.Offset(0, -6).Value = c.Offset(0, -6).End(xlUp).Value
End If
End If
Next c
End Sub
Only problem is it do not do anything if the reference cell is empty in the column G. I think you don't need that anyway.
Hope this help.
It will run on your current selection
Copy the whole code into a module, run the fill_down() to fill down, fill_up() to fill up.
'======================
'******Filling*********
'======================
Sub fill_up()
Selection.SpecialCells(xlCellTypeBlanks).Select
Selection.FormulaR1C1 = "=R[1]C"
End Sub
Sub fill_down()
Call copy_last(Selection)
Call filling
End Sub
Function copy_last(r As range)
Dim arr() As Variant
Dim x As Double
Dim arr_size As Double
arr = r
arr_size = UBound(arr, 1)
For x = arr_size To 1 Step -1
If Not isempty(arr(x, 1)) Then
Exit For
End If
Next x
r(r.Row, 1) = arr(x, 1)
End Function
Function filling()
Selection.SpecialCells(xlCellTypeBlanks).Select
Selection.FormulaR1C1 = "=R[-1]C"
End Function
'=======================
'******End filling******
'=======================

Excel VBA change based on pull down list

I have a few Excel formulas that change data in columns E & G based on the info entered into columns C & D. Column F needed to be a static time stamp so I had to use a simple VBA script for it. The formulas are a little long and unwieldy, and other people work on the workbook, so I tried scripting E & G through VBA to lower the risk of the formulas getting messed up.
I'm not quite the best when it comes to VBA, and after numerous failed attempts, I've ended up with what just ends up crashing Excel.
The following is my latest attempt;
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 4 Then
Cells(Target.Row, 6).Value = Now
End If
If Target.Column = 4 And Cells(Target.Row, 4).Value = "Daily" Then
Cells(Target.Row, 5).Value = [INDIRECT("C" & ROW())+28]
ElseIf Target.Column = 4 And Cells(Target.Row, 4).Value = "Weekly" Then
Cells(Target.Row, 5).Value = [INDIRECT("C" & ROW())+49]
Else: Cells(Target.Row, 5).Value = "---"
End If
End Sub
I also have the following which I haven't tried due to the other part crashing;
If Target.Column = 4 And (Cells(Target.Row, 4).Value = "Daily" OR Cells(Target.Row, 4).Value = "Weekly") Then
Cells(Target.Row, 7).Value = [WORKDAY(INDIRECT(""E"" & ROW()),-1]
Else Cells(Target.Row, 7).Value = "---"
End If
And the Excel formulas;
=IF(INDIRECT("D" & ROW())<>"",CHOOSE(IF(INDIRECT("D" & ROW())="Daily",1,IF(INDIRECT("D" & ROW())="Weekly",2,3)),INDIRECT("C" & ROW())+28,INDIRECT("C" & ROW())+49,"---"),"")
=IF(INDIRECT("D" & ROW())<>"",CHOOSE(IF(OR(INDIRECT("D" & ROW())="Daily",INDIRECT("D" & ROW())="Weekly"),1,2),WORKDAY(INDIRECT("E" & ROW()),-1),"---"),"")
I've tried recording a macro of the formulas and copying the code from there into the VBA code, but that didn't work either.
I said it in the title, but forgot to mention in the body; Column D is a drop down list, Column C is a date that the user enters.
Your logic is not bad, but the structure of the event macro needs improvement. For this kind of event we need:
Private Sub Worksheet_Change(ByVal Target As Range)
'if Target is NOT of interest, then exit
Application.EnableEvents = False
'perform your logic
Application.EnableEvents = True
End Sub
This prevents the macro from stepping on its own tail.
This is a better schema (untested)
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Count > 1 Then Exit Sub
If Target.Column <> 4 Then Exit Sub
Dim v As String
v = Target.Value
Application.EnableEvents = False
Target.Offset(0, 2).Value = Now
Target.Offset(0, 1).Value = "'----"
If v = "Daily" Then
'insert formula
End If
If v = "Weekly" Then
'insert formula
End If
Application.EnableEvents = True
End Sub
On a separate issue is having VBA insert a formula. Here the schema is like:
Cells(3,7).Formula="=1+2"
There may be other problems.

Creating an inventory system using ComboBox/UserForms

I'm attempting to create an inventory system at my work as the only software we have as Excel. Basically we have a Work order sheet that we enter the repairs as well as parts used on. I made a code that would pull the inserted part numbers & descriptions out of the individual work orders to keep track of everything used, but my boss wants me to create a system that will allow us to start typing the name/part number of something and have it guess or fill in for us. Hence where the combobox comes in.
I got It working up to a point. The lists are populated with the part inventory (we have a master EXTNERAL file listing) but my issue is this:
When you click the "add part" of the user form, I can't figure out how to have the parts be added in a certain range on the Work Order. All the tutorials I've been following here and here only have it set up to add the parts in order of the column. Can anybody look at my (terrible, I'm sorry) coding and see if they can help?
Private Sub UserForm_Initialize()
Dim cPart As Range
Dim cNum As Range
Dim ws As Workbook
'Dim ComboBox1 As Variant
Application.ScreenUpdating = False
Set ws = Workbooks.Open("\\Capserver\iso maintenance\CAPS MASTER PARTS & PRICE LIST 2012.xls")
Windows("CAPS MASTER PARTS & PRICE LIST 2012.xls").Visible = False
'ws.Sheets("CAPS ORDER FORM").Range("Name") = Sheet1.ComboBox1
'ComboBox1.Clear
For Each cPart In ws.Sheets("CAPS ORDER FORM").Range("Name")
With Me.cboPart
.AddItem cPart.Value
End With
Next cPart
For Each cNum In ws.Sheets("CAPS ORDER FORM").Range("Number")
With Me.cboNum
.AddItem cNum.Value
.List(.ListCount - 1, 1) = cNum.Offset(0, 1).Value
End With
Next cNum
End Sub
Private Sub cmdAdd_Click()
Dim lRow As Range
Dim lPart As Long
Dim ws As Worksheet
Dim something As Variant
Dim box As Object
Set ws = Worksheets("Sheet2")
With Worksheets(1).Range("A1:a500")
Set lRow = .Find(What:="", SearchOrder:=xlRows, SearchDirection:=xlNext, LookIn:=xlValues)
End With
'Set lRow = Range("A1")
' If VBA.IsEmpty(lRow.Value) Then
' MsgBox ("POOP!")
' Else
' Set box = lRow.End(xlDown)
' End If
'lRow = Worksheets("Sheet2").Range("A33:A37")
'ws.Cells.Find(What:="*", SearchOrder:=xlRows, (From tutorial, always returned lRow = Nothing)
' SearchDirection:=xlPrevious, LookIn:=xlValues).Row 1
lPart = Me.cboPart.ListIndex
'check for a part number
If Trim(Me.cboPart.Value) = "" Then
Me.cboPart.SetFocus
MsgBox "Please enter a part name or number"
Exit Sub
End If
'copy the data to the database
'use protect and unprotect lines,
' with your password
' if worksheet is protected
With ws
' .Unprotect Password:="password"
.Cells(lRow, 1).Value = Me.cboPart.Value
.Cells(lRow, 2).Value = Me.cboPart.List(lPart, 1)
.Cells(lRow, 3).Value = Me.cboNum.Value
' .Cells(lRow, 4).Value = Me.txtDate.Value
.Cells(lRow, 5).Value = Me.txtQty.Value
' .Protect Password:="password"
End With
'Combobox1.linkedcell=C4
'clear the data
Me.cboPart.Value = ""
Me.cboNum.Value = ""
Me.txtQty.Value = ""
Me.cboPart.SetFocus
End Sub
Private Sub cmdClose_Click()
Unload Me
End Sub
The goal is to be able to click the "Add part" button and add multiple files and have it output to the work order (I think the range for the parts is A33:A55 or something similar)
I ALSO would like to know if there is a way to make BOTH the part name AND part numbers dependent in the UserForm depending on which one you enter? Though that is a lower priority.
I'm still not 100% on what you are trying to do, and you may have some sections commented out that you may want to use (ie the combobox). Your 1row range is finding the next empty cell which I don't think you want to do. But as for how you would input the information into a range, you probably need to change your ws With statement:
1row = 35 'or whatever row number
For n =0 to CountOfItemsToAdd 'could also be done with a For Each statement
'You will also need another for statement here to go through your part list
With ws
1row = 1row + (n*3)
' .Unprotect Password:="password" [you only need this if using passwords]
.Cells(lRow, 1).Value = Me.cboPart.Value
.Cells(lRow+1, 1).Value = Me.cboPart.List(lPart, 1)
.Cells(lRow+2, 1).Value = Me.cboNum.Value
' .Cells(lRow, 4).Value = Me.txtDate.Value
.Cells(lRow+3, 1).Value = Me.txtQty.Value
' .Protect Password:="pasword"
But to awnser your specific question, you change the first value in the .Cells reference which is the row index, not the second number, which is the column index.