Below is a program in Excel VBA that creates a progress indicator. I've tried to make the progress indicator as simple as possible, yet it still looks elegant by using Unicode characters: full block and thin space.
Private Sub Play_Click()
Dim iCounter As Long, iRow As Long, nRow As Long, _
Block As String, Progress As Long, iChar As Long
Columns(1).ClearContents
With Cells(2, 4)
.ClearContents
.Font.Color = vbBlue
nRow = 100
For iRow = 1 To nRow
For iCounter = 1 To 100
Cells(iRow, 1) = iCounter
Next
Progress = Int(iRow / 10)
If Progress = iRow / 10 Then
Block = Block & ChrW(9608) & ChrW(8201)
'------------------
'Option statements
'------------------
End If
.Value = Block & " " & iRow & " %"
Next
End With
End Sub
I'd like to have the progress indicator looks like this
where the full blocks are always green-colored and the percentage number is always blue-colored while the program is running. But using these three option statements,
Option 1
.Characters(, 2 * Progress - 1).Font.Color = vbGreen
Option 2
For iChar = 1 To Len(.Value)
If Mid$(Text, iChar, 1) = ChrW(9608) Then
.Characters(iChar, 1).Font.Color = vbGreen
End If
Next
Option 3
GreenBlue 2 * Progress - 1
---------------------
Sub GreenBlue(GreenPart As Integer)
Select Case GreenPart
Case 1 To 19
Cells(2, 4).Characters(, GreenPart).Font.Color = vbGreen
End Select
End Sub
I kept getting the following output
What is the correct way to get the output like the first picture?
Whenever you replace the value of the cell, all of the new content will pick up its formatting from the first character being replaced, so the whole content will be green: need to first set the color back to blue if you want the numeric part to be blue
Private Sub Play_Click()
Dim iCounter As Long, iRow As Long, nRow As Long, _
Block As String, Progress As Long, iChar As Long, x As Long
Columns(1).ClearContents
With Cells(2, 4)
.ClearContents
.Font.Color = vbBlue
nRow = 100
For iRow = 1 To nRow
For iCounter = 1 To 100
Cells(iRow, 1) = iCounter
Next
Progress = Int(iRow / 10)
If Progress = iRow / 10 Then
Block = Block & ChrW(9608) & ChrW(8201)
End If
Application.ScreenUpdating = False 'reduce flashing during update
.Value = Block & " " & iRow & " %"
.Font.Color = vbBlue
If Len(Block) > 0 Then
.Characters(1, InStr(.Value, " ")).Font.Color = vbGreen
End If
Application.ScreenUpdating = True
'add some delay...
For x = 1 To 1000
DoEvents
Next x
Next
End With
End Sub
Related
I have a workbook with several sheets. On every sheet i repet row 1 to 13 on each page. In cell C7 i want page number and total number of pages in the format of "1/3".
I have found this VBA code to do part of the problem
placed this part in a module
Sub PageNumber(MyRange As String)
Dim iVPC As Integer
Dim iHPC As Integer
Dim iVPB As VPageBreak
Dim iHPB As HPageBreak
Dim iNumPage As Integer
iHPC = 1
iVPC = 1
If ActiveSheet.PageSetup.Order = xlDownThenOver Then
iHPC = ActiveSheet.HPageBreaks.Count + 1
Else
iVPC = ActiveSheet.VPageBreaks.Count + 1
End If
iNumPage = 1
For Each iVPB In ActiveSheet.VPageBreaks
If iVPB.Location.Column > ActiveCell.Column Then Exit For
iNumPage = iNumPage + iHPC
Next
For Each iHPB In ActiveSheet.HPageBreaks
If iHPB.Location.Row > ActiveCell.Row Then Exit For
iNumPage = iNumPage + iVPC
Next
MyRange = "'" & iNumPage & "/" & Application.ExecuteExcel4Macro("GET.DOCUMENT(50)")
End Sub
placed this part in the sheet object window
Private Sub Worksheet_Activate()
Dim StrString As String
Call PageNumber(StrString)
Range("C7:C7").Value = StrString
End Sub
The part i have problems with is that i need iNumPage (the current page number, not the total number of pages)to update for every page that is going in to print.
Is there a way maybe piggyback riding of the page number function from header and footer, and using it in Private Sub Workbook_BeforePrint or is there another solution. The page number is only for cell C7 because of the repet of row 1-13 on top of each page in print out.
I've asked this question on another site and this was as close we could get, but it's not a good solution because of the handling of print property's, the documents prints once for each page and that will cause trouble if you what print on both sides or in a pdf file.
Sub Print_This_Sheet()
Dim i, iTot_pages
Dim lastrow As Long
Dim LastColumn As Long
Dim sht As Worksheet
Set sht = ActiveSheet
Dim LastColumStr As String
lastrow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
LastColumn = sht.Cells(1, sht.Columns.Count).End(xlToLeft).Column
LastColumStr = getColLet(LastColumn)
Application.EnableEvents = False ' no interference with workbook_BeforePrint !!!!
With ActiveSheet
.PageSetup.PrintArea = "A1:" & LastColumStr & lastrow
.PageSetup.PrintTitleRows = "$1:$13"
MsgBox "and other pagesetup-setting ....", vbInformation
iTot_pages = Application.ExecuteExcel4Macro("GET.DOCUMENT(50)") '----> normally the number of pages
For i = 1 To iTot_pages 'loop through those pages
.Range("C7").Value = "'" & i & "/" & iTot_pages 'before printing, ajust C7
.PrintOut i, i, 1, True 'PrintOut with previeuw page per page'print one page
Next
End With
Application.EnableEvents = True 'enable events again
End Sub
Public Function getColLet(colNum As Long) As String
Dim i As Long, x As Long
'If Not isBetween(colNum, 1, Application.Columns.count) Then Exit Function
For i = Int(Log(CDbl(25 * (CDbl(colNum) + 1))) / Log(26)) - 1 To 0 Step -1
x = (26 ^ (i + 1) - 1) / 25 - 1
If colNum > x Then getColLet = getColLet & Chr(((colNum - x - 1) \ 26 ^ i) Mod 26 + 65)
Next i
End Function
Contributor to this solution mcranmoss, the function is from a previous project so it's from somewhere....
I want to increment the decimal part of a number and restart numbering every time the number changes as below
1.00
1.01
1.02
1.03
1.04
1.05
2.00 'Restart With 2
2.01
3.00 'Restart With 3
3.01
3.02
3.03
I used the following Code
Sub AutoNumberDecimals()
Dim Rng, C As Range
Dim Lrow As Long
Dim i As Integer
Lrow = Cells(Rows.Count, 1).End(xlUp).Row
Set Rng = Worksheets("Union").Range("A2:A" & Lrow)
For Each C In Rng.Cells
If C.Value = "" And C.Offset(0, 1).Value = "" Then
C.Offset(1, 0).Value = C.Value + 0.01
Next C
End Sub
But It did not work
Appreciate your help
Thanks, Regards
I wrote this code. Make sure to add it in Sheet1 module (or similar sheet). It reacts when you enter a number in column 1 and it renumbers all numbers in that column. If you enter 1, it shows 1.00... if you enter 1 again, it will show 1.01. If you enter 2 you will have 2.00 etc...
Private ChangingValues As Boolean
Private Sub RenumFirstColumn()
Dim RowNo As Integer
Dim Major As Integer
Dim Minor As Integer
Dim CurrentValue As String
RowNo = 1
Major = 1
Minor = 0
Do
CurrentValue = CStr(Cells(RowNo, 1).Value)
If Int(Val(Left(CurrentValue, 1))) = Major Then
CurrentValue = CStr(Major) & "." & Format(Minor, "00")
Minor = Minor + 1
If Minor > 99 Then
MsgBox "To high value (> X.99)"
Exit Sub
End If
Else
Major = Val(Left(CurrentValue, 1))
Minor = 0
CurrentValue = CStr(Major) & "." & Format(Minor, "00")
Minor = Minor + 1
End If
Cells(RowNo, 1).NumberFormat = "#"
Cells(RowNo, 1).Value = CurrentValue
RowNo = RowNo + 1
Loop Until IsEmpty(Cells(RowNo, 1))
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 And ChangingValues = False Then
ChangingValues = True
RenumFirstColumn
ChangingValues = False
End If
End Sub
Hope it was what you were looking for
Try the next code, please. It uses maxIncr variable to set a maximum incrementing times:
Sub IncrementingRoots()
Dim sh As Worksheet, lastR As Long, maxIncr As Long
Dim NrI As Long, i As Long, j As Long
Set sh = ActiveSheet: maxIncr = 7
lastR = sh.Range("A" & Rows.count).End(xlUp).Row
For i = 2 To lastR + maxIncr
If sh.Range("A" & i).Value <> "" Then
NrI = sh.Range("A" & i).Value
For j = 1 To maxIncr
If sh.Range("A" & i + j).Value = Empty Then
sh.Range("A" & i + j).Value = sh.Range("A" & i + j - 1).Value + 0.01
Else
i = j + i - 1: Exit For
End If
Next
End If
If i > lastR Then Exit For
Next i
End Sub
And the next code is yours adapted to work. But impossible to procress the last number in the range, too, without something more (like maxIncr in my above code)...
Sub AutoNumberDecimals()
Dim sh As Worksheet, Rng As Range, C As Range, Lrow As Long, i As Long
Set sh = ActiveSheet 'Worksheets("Union")
Lrow = sh.cells(Rows.count, 1).End(xlUp).Row
Set Rng = sh.Range("A2:A" & Lrow)
For Each C In Rng.cells
If C.Value = "" And (C.Offset(1, 0).Value <> _
Int(C.Value Or C.Offset(1, 0).Value = "")) Then
C.Value = C.Offset(-1, 0).Value + 0.01
End If
Next C
End Sub
This uses DataSeries and NumberFormat to fill the cells.
This creates a random board, and isn't necessary to the main code.
Cells.Clear
Cells(1, 1) = 1 ' creates a random board
x = 2
For i = 2 To 20
If Rnd() > 0.8 Then
Cells(i, 1) = x
x = x + 1
End If
Next i
Cells(21, 1) = 0 ' terminates entries
Note that rather than determine the row column length using code, I have preset it to 21, although you can use the terminating 0.00 value to define a column length.
The main code:
Range("a:a").NumberFormat = "0.00"
For i = 1 To 21 ' loops through range
j = 0 ' finds local range
If Cells(i, 1) <> "" And Cells(i, 1) > 0 Then
Do
j = j + 1
Loop While Cells(i + j, 1) = ""
End If
Range(Cells(i, 1), Cells(i + j - 1, 1)).DataSeries Type:=xlLinear, Step:=0.01
i = i + j ' jumps to next entry
Next i
Each cell is formatted into the desired style. Then the loop finds a non-empty cell, and determines the associated local subrange by checking if the next cell down is empty or not, and continues until it isn't. Then the subrange is formatted using DataSeries with a Step of 0.01.
Ref:
https://learn.microsoft.com/en-us/office/vba/api/excel.range.dataseries
I am trying to compare values in two lists. I want my code to compare a value in the first list and check all the entries in the second list. If there is a match then the code will print true next to the value in the first list and if not it will print false.
The problem I am having is that my code only compares values that are in the same row.
The code runs and I have tried it on a two smaller lists to make sure the data types are to same and there aren't any extra spaces or commas in the lists that would lead to a "False" output. I have also tried changing the order of the for and if statements but this doesn't work either.
Sub findvalues()
For i = 2 To 16
For j = 2 To 16
If Cells(i, 3).Value = Cells(i, 1).Value Then
Cells(i, 4).Value = "TRUE"
ElseIf Cells(i, 3).Value = Cells(j + 1, 1).Value Then
Cells(i, 4).Value = "TRUE"
Else
Cells(i, 4).Value = "FALSE"
End If
Next j
Next i
End Sub
Here are the two lists I am testing the code on
Slight mods to your code based on the data you provided in columns 1 & 3. As always, things could be improved but this should get you going ...
Sub findvalues()
Dim i As Long, j As Long, bResult As Boolean
For i = 2 To 16
strValueToLookFor = Cells(i, 1)
For j = 2 To 16
bResult = False
If strValueToLookFor = Cells(j, 3).Value Then
bResult = True
Exit For
End If
Next j
Cells(i, 6).Value = bResult
Next i
End Sub
... you may just need to flick the columns over so the first list searches on the second list or vice versa.
I don't see any need for VBA - formulas are the way to go - but to avoid two loops one could do this:
Sub findvalues()
Dim i As Long
For i = 2 To 130
Cells(i, 4).Value = IsNumeric(Application.Match(Cells(i, 1).Value, Range("C2:C130"), 0))
Next i
End Sub
Update: this does not cater for multiple matches.
There are many was to achieve that. one of them is by using IF & COUNTIF
Formula
=IF(COUNTIF($E$2:$E$6,A2)>0,"TRUE","FALSE")
Results:
VBA CODE
Option Explicit
Sub findvalues()
Dim i As Long
Dim rng As Range
With ThisWorkbook.Worksheets("Sheet1") 'Change if needed
Set rng = .Range("A2:A130") 'set rng to includes values from column A, rows 2:130
For i = 2 To 130 'Loop from row 2 to 130
'Check if the values in column C includes in the rng
If Application.WorksheetFunction.CountIf(rng, .Range("C" & i).Value) > 0 Then
.Range("D" & i).Value = "TRUE"
Else
.Range("D" & i).Value = "FALSE"
End If
Next i
End With
End Sub
VBA code to reconcile two lists.
Sub Reconciliation()
Dim endRow As Long
Dim ICount As Long
Dim Match1() As Variant
Dim Match2() As Variant
Dim ws As Worksheet
Set ws = Worksheets("Recon")
ICount = 0
endRow = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row
endRow1 = ws.Cells(ws.Rows.Count, 11).End(xlUp).Row
Match1 = Sheet1.Range("b2:b" & endRow)
Match2 = Sheet1.Range("K2:K" & endRow1)
For i = LBound(Match1) To UBound(Match1)
For j = LBound(Match2) To UBound(Match2)
If Match1(i, 1) = Match2(j, 1) Then
ICount = ICount + 1
Sheet1.Range("C" & i + 1).Value = ICount
Sheet1.Range("L" & j + 1).Value = ICount
Else
End If
Next j
Next i
End Sub
I need help improving code or if my code is already pretty good, I'd be interested in alternative ways to do it.
What I want to do:
In Excel, I have a userform in which the user types people's name, age, hair color etc. (one person at a time). For something like hair color, I have given 5 pre-defined choices in a listbox and since people can change hair color, multiselect is enabled. The selected hair color (one or multiple) is then pasted to a specific cell.
Problem:
I've struggled a bit with error handling when the user forgets to choose a hair color.
Working code: I did get it to work with the following code
Private Sub cmdSubmit_Click()
Dim cnt As Long
Dim LastRow As Long
Dim s As String
Dim i As Integer
With Me.lbxHair
For i = 0 To .ListCount - 1
If .Selected(i) = True Then
s = s & .List(i) & ","
cnt = cnt + 1
End If
Next i
End With
If cnt = 0 Then
MsgBox "No hair color selected"
Exit Sub
Else
Cells(LastRow + 1, 1).Value = Me.tbxName.Value
Me.tbxName.Value = ""
Me.tbxName.SetFocus
Range("B" & LastRow + 1).Value = Left(s, Len(s) - 1)
On Error Resume Next
End If
End Sub
This is perfectly fine for my purposes, but is there a way to do it without the auxiliary cnt-Variable? I tried this because I've read .ListIndex = -1 means nothing is selected
Non-working code (same variable declaration as above):
With Me.lbxHair
If .ListIndex = -1 Then
MsgBox "No hair color selected"
Exit Sub
Else
For i = 0 To .ListCount - 1
If .Selected(i) = True Then s = s & .List(i) & ","
Next i
End If
End With
Cells(LastRow + 1, 1).Value = Me.tbxName.Value
Me.tbxName.Value = ""
Me.tbxName.SetFocus
Range("B" & LastRow + 1).Value = Left(s, Len(s) - 1)
On Error Resume Next
When trying to not select anything, I get "Run time error '5': Invalid procedure call or argument"
Why? Also, do you have any other suggestions how I could go about this or how I could improve my code?
You can try something like this:
Private Sub cmdSubmit_Click()
Dim LastRow As Long
Dim s As String, sep As String
Dim i As Integer
With Me.lbxHair
For i = 0 To .ListCount - 1
If .Selected(i) = True Then
s = s & sep & .List(i)
sep = ","
End If
Next i
End With
If Len(s) = 0 Then
MsgBox "No hair color selected"
Exit Sub
Else
Cells(LastRow + 1, 1).Value = Me.tbxName.Value
Cells(LastRow + 1, 2).Value = s
Me.tbxName.Value = ""
Me.tbxName.SetFocus
End If
End Sub
I'm trying to utilize a dynamically created userform and based on what boxes are check, gray out certain cells.
As background, this is for an injection molding facility. QA sets up the cavity numbers that are running. This dynamic userform creates checkboxes based on the cavity numbers that are input on the worksheet.
Option Explicit
Private Sub UserForm_Initialize()
Dim col As Long
Dim row As Long
Dim lcol As Long
Dim i As Long
Dim j As Long
Dim chkBox As MsForms.CheckBox
Dim l As MsForms.Frame
Dim t As MsForms.Label
Set l = Me.Controls.Add("Forms.Frame.1", "cavz", True)
l.Caption = "BLOCKED CAVITIES"
l.Height = 195
Set t = l.Controls.Add("Forms.Label.1", "mark")
t.Caption = "Mark all cavities that are currently blocked:"
t.Width = 175
t.Top = 10
col = 2 'Set your column index here
row = 8
lcol = 17
j = 1
For i = col To lcol
Set chkBox = l.Controls.Add("Forms.CheckBox.1", "CheckBox_" & i)
If Worksheets("QA").Cells(row, i).Value <> "" Then
chkBox.Caption = Worksheets("QA").Cells(row, i).Value
ElseIf Worksheets("QA").Cells(row, i).Value = "" Then
GoTo 10
End If
If i <= 9 Then
'MsgBox "i = " & i
chkBox.Left = 5
chkBox.Top = 5 + ((i - 1) * 20)
ElseIf i > 9 Then
j = j + 1
'MsgBox "j = " & j
chkBox.Left = 100
chkBox.Top = 5 + ((j - 1) * 20)
End If
10
Next i
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Private Sub CommandButton1_Click()
Dim x As Control
Dim cavz As MsForms.Frame
For Each x In cavz.Controls
If x.Value = True Then
If x.Value = Range("B8") Then
Range("B8:B14").Select
Selection.Interior.ColorIndex = 16
End If
End If
Next x
End Sub
I started out doing one thing, and somehow this is what my code turned into. It's not the neatest code, but I would still consider myself a novice at this. Plus, this is the first time I couldn't find the answer on my own and thus my first time asking for help. So, any help would be greatly appreciated!
Thanks!
UPDATE
This is great! Thank you Don. However, I do have a follow up question. Perhaps I've been banging my head against this for too long, but I cannot think of a more efficient way to do this. Here is what I'm now doing:
For i = 2 To 17
Set ctl = Controls.Item("CheckBox_" & i)
If ctl = True Then
If i = 2 Then
Range(Cells(j, c), Cells(m, c)).Select
Selection.Interior.ColorIndex = 16
Range(Cells(k, c), Cells(m, c)).Select
With Selection
.Merge
Cells(k, c) = "BLOCKED"
.Orientation = 90
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
End With
End If
Next i
etc
I'm in the process of writing if i = 1 through 17, but I feel like there's a more efficient way and I just can't crack it.
Loop through them by name the same way you created them.
Dim ctl As Control
For i = 2 To 17
Set ctl = Controls.Item("CheckBox_" & i)
Next 'i
Also, I would create module level constants for the initial values of col, lcol, etc. and reuse those in both routines.