Command button press cycles through a desired range - excel

I'm trying to write code to where on each command button press, the current time is put into the first cell in a range. Then on the next button click, the next cell in the range is filled with the current time, and so on. I cant figure out how to cycle through the desired range and place a time value at that cell on each button press.
I have a basic double For loop that goes through the entire range I want and populates all cells with the current time at once. I only want one cell to populate at a time with the current time on each button click, and I cant figure out how for the life of me.
code so far:
Private Sub CommandButton1_Click()
Dim i As Integer, j As Integer
For i = 6 To 115
For j = 3 To 5
Cells(i, j).Value = Time
Next j
Next i
End Sub

I understand it like that
Private Sub CommandButton1_Click()
Dim rg As Range
Set rg = Range("C6:E115")
Dim sngCell As Range
For Each sngCell In rg
If Len(sngCell.Value) = 0 Then
sngCell.Value = Time
Exit For
End If
Next
End Sub
Update:
This solution should be faster but I think it will not be noticeable.
Private Sub CommandButton1_Click()
Dim rg As Range
Set rg = Union(Range("C5"), Range("C6:E115"))
Dim nextCell As Range
Set nextCell = rg.Find("")
If Not nextCell Is Nothing And nextCell.Address <> "$C$5" Then
nextCell.Value = Time
End If
End Sub

this should do the trick:
Option Explicit
Private Sub CommandButton1_Click()
Dim i As Integer, j As Integer
Dim emptyCellFound As Boolean
Dim c As Range
emptyCellFound = False 'not really needed. just for clarity
For i = 6 To 115
For j = 3 To 5
Set c = Cells(i, j)
If c.Value = "" And Not emptyCellFound Then
c.Value = Time
emptyCellFound = True
End If
Next j
Next i
End Sub

This works if any value is applied in any cell below row 114
Private Sub CommandButton1_Click()
On Error Resume Next
[C6:E115].SpecialCells(xlCellTypeBlanks).Cells(1).Value = Time
On Error GoTo 0
End Sub
..without a value below row 114 this works
Private Sub CommandButton1_Click()
On Error GoTo weird
[C6:E115].SpecialCells(xlCellTypeBlanks).Cells(1).Value = Time
Exit Sub
weird:
If [C115].End(xlUp).Row > 6 Then [C115].End(xlUp).Offset(1).Value = Time
On Error GoTo 0
End Sub

Related

Merging two identical modules in Excel VBA

Could help me merge these two modules, so I can use them more than only one time?
First module:
Private Sub UserForm_Initialize()
Dim ultimaLin As Long, area As New Collection
Dim Value As Variant, temp() As Variant
On Error Resume Next
ultimaLin = Sheets("DBTemp").Range("A" & Rows.Count).End(xlUp).Row
temp = Sheets("DBTemp").Range("A2:A" & ultimaLin).Value
For Each Value In temp
If Len(Value) > 0 Then area.Add Value, CStr(Value)
Next Value
For Each Value In area
titulo_livro.AddItem Value
Next Value
Set area = Nothing
End Sub
Second module:
Private Sub UserForm_Initialize()
Dim ultimaLin As Long, area As New Collection
Dim Value As Variant, temp() As Variant
On Error Resume Next
ultimaLin = Sheets("DBTemp").Range("B" & Rows.Count).End(xlUp).Row
temp = Sheets("DBTemp").Range("B2:B" & ultimaLin).Value
For Each Value In temp
If Len(Value) > 0 Then area.Add Value, CStr(Value)
Next Value
For Each Value In area
autor_livro.AddItem Value
Next Value
Set area = Nothing
End Sub
As you can see it, they are basically the same thing, but in the second one I want to reproduce the obtained result in another range.
Thanks!
You can factor out the common parts of the code into re-usable methods.
Example Form module code:
Private Sub UserForm_Initialize()
With ThisWorkbook.Sheets("DBTemp")
FillFromRange .Range("A2:A" & .Cells(.Rows.Count, "A").End(xlUp).Row), _
titulo_livro
End With
End Sub
In a regular module:
Sub FillFromRange(rng As Range, ctrl As Object)
Dim v
For Each v In UniquesFromRange(rng)
ctrl.AddItem v
Next v
End Sub
Function UniquesFromRange(rng As Range)
Dim col As New Collection, data, v
data = rng.Value
For Each v In data
If Len(v) > 0 Then
On Error Resume Next
col.Add v, CStr(v)
On Error GoTo 0
End If
Next v
Set UniquesFromRange = col
End Function

Passing a dynamic range to charts

I want to check the status of a sheet and when changed automatically run some calculations. I also wish refresh a graph with the new data from that sheet.
I used the Worksheet_Change function. It calls the sub with the calculations and calls the sub that contains the chart modification code. They run as planned with one exception. The range that gets passed to the Chrt1 sub (responsible for the chart functionality) does not get updated on the graph once it has been called out for the first time.
I'm aware that this can be overcome with Excel built-in tables function but I'd like to code this simple routine in anyways.
The Worksheet_Change function:
Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
AutoChangeTest
Application.EnableEvents = True
End Sub
The main module code:
Sub AutoChangeTest()
Dim s1 As Worksheet, s2 As Worksheet
Dim i As Integer, j As Integer, lrow As Integer, lrow2 As Integer
Set s1 = Sheets("Arkusz3")
On Error GoTo Err1
lrow = s1.Cells(s1.Rows.Count, 1).End(xlUp).Row
For i = 1 To lrow
s1.Cells(i, 2) = s1.Cells(i, 1) * 2
Next
Call Chrt1(Range(s1.Cells(1, 1), s1.Cells(lrow, 2)), s1)
Err1:
If Not IsNumeric(s1.Cells(i, 1)) Then
s1.Cells(i, 1).Activate
End If
End Sub
Sub Chrt1(r1 As Range, s1 As Worksheet)
Dim c1 As Shape
Dim s As Worksheet
Dim cht As ChartObject
Dim i As Integer
i = 0
Set r = r1
Set s = s1
For Each cht In s.ChartObjects
i = i + 1
Next
If i = 0 Then
Set c1 = s.Shapes.AddChart
End If
c1.Chart.SetSourceData (r)
End Sub
Some suggestions in the code below:
Sub AutoChangeTest()
Dim ws As Worksheet 'avoid variable names with 1/l - too unclear
Dim i As Long, lrow As Long 'always use long over integer
Set ws = ThisWorkbook.Worksheets("Arkusz3")
lrow = ws.Cells(ws.Rows.Count, 1).End(xlUp).row
On Error GoTo exitHere
Application.EnableEvents = False 'don't re-trigger this sub...
For i = 1 To lrow
With ws.Cells(i, 1)
'easier to test than to trap an error if non-numeric
If IsNumeric(.Value) Then
ws.Cells(i, 2) = .Value * 2
Else
ws.Select
.Select
MsgBox "Non-numeric value found!"
GoTo exitHere 'acceptable use of Goto I think
End If
End With
Next
'don't think you need a separate method for this...
If ws.ChartObjects.Count = 0 Then ws.Shapes.AddChart 'no need to loop for a count
'assuming there will only be one chart...
ws.ChartObjects(1).Chart.SetSourceData ws.Range(ws.Cells(1, 1), ws.Cells(lrow, 2))
exitHere:
If Err.Number <> 0 Then Debug.Print Err.Description
Application.EnableEvents = True
End Sub
In your Chrt1 procedure, this bit
For Each cht In s.ChartObjects
i = i + 1
Next
If i = 0 Then
Set c1 = s.Shapes.AddChart
End If
can be replaced by the following:
If s.ChartObjects.Count = 0 Then
Set c1 = s.Shapes.AddChart
End If
But what is c1 if you don't have to add a chart? You haven't defined it, and the On Error means you never find out that it's broken.
Assuming you want the last chart object to be the one that is changed:
If s.ChartObjects.Count = 0 Then
Set c1 = s.Shapes.AddChart
Else
Set c1 = s.ChartObjects(s.ChartObjects.Count)
End If
And you should declare c1 as a ChartObject.
Finally, remove the parentheses around r in this line:
c1.Chart.SetSourceData r
Thank you all for support. The basic code that works is shown below. It isn't the best looking but it does the job.
Sub Chrt1(r1 As Range, s1 As Worksheet)
Dim c1 As Shape
Dim s As Worksheet
Dim cht As ChartObject
Dim i As Integer
i = 0
Set r = r1
Set s = s1
For Each cht In s.ChartObjects
i = i + 1
Next
If i = 0 Then
Set c1 = s.Shapes.AddChart
End If
Set cht = s.ChartObjects(1)
cht.Chart.SetSourceData Source:=r
End Sub

How to continue the sequence of the unique numbers in the excel sheet after closing the userform?

I am facing a problem in getting the sequence of the unique numbers(Serial number) when the userform is closed and opened later on. Firstly, when I fill the data in the userform everything is captured in the excel sheet perfectly with correct sequence; if I close the userform and run the code by filling the userform with new data the unique ID's are again starting from "1" but not according to the excel sheet row number which was previously saved.
Below is the code I tried:
Private Sub cmdSubmit_Click()
Dim WB As Workbook
Dim lr As Long
Set WB = Workbooks.Open("C:\Users\Desktop\Book2.xlsx")
Dim Database As Worksheet
Set Database = WB.Worksheets("Sheet1")
eRow = Database.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Row
lr = Database.Range("a65536").End(xlUp).Row
With Sheets("Sheet1")
If IsEmpty(.Range("A1")) Then
.Range("A1").Value = 0
Else
Database.Cells(lr + 1, 1) = Val(Database.Cells(lr, 1)) + 1
End If
End With
Database.Cells(eRow, 4).Value = cmbls.Text
Database.Cells(eRow, 2).Value = txtProject.Text
Database.Cells(eRow, 3).Value = txtEovia.Text
Database.Cells(eRow, 1).Value = txtUid.Text
Call UserForm_Initialize
WB.SaveAs ("C:\Users\Desktop\Book2.xlsx")
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
Dim maxNumber
If Not Intersect(Target, Range("B:B")) Is Nothing Then
' don't run when more than one row is changed
If Target.Rows.Count > 1 Then Exit Sub
' if column A in the current row has a value, don't run
If Cells(Target.Row, 1) > 0 Then Exit Sub
' get the highest number in column A, then add 1 and write to the
' current row, column A
maxNumber = Application.WorksheetFunction.Max(Range("A:A"))
Target.Offset(0, -1) = maxNumber + 1
End If
End Sub
Private Sub UserForm_Initialize()
With txtUid
.Value = Format(Val(Cells(Rows.Count, 1).End(xlUp)) + 1, "0000")
.Enabled = False
End With
With txtProject
.Value = ""
.SetFocus
End With
End Sub
In this image if you see unique id's are repeating 1 and 2, but I need as 1,2,3,4....
I think this is where the issue is coming from. You need to re-calculate the last row every time the user form is Initialized.
Private Sub UserForm_Initialize()
Dim ws as Worksheet: Set ws = Thisworkbook.Sheets("Database")
With txtUid
.Value = Format(ws.Range("A" & ws.Rows.Count).End(xlUp) + 1, "0000")
.Enabled = False
End With
With txtProject
.Value = ""
.SetFocus
End With
End Sub
It's always risky to use row numbers or [max range value +1] as a sequence number.
Safer to use something like a name scoped to the worksheet, which has a value you can increment. Then the sequence is independent of your data.
E.g.
Function GetNextSequence(sht As Worksheet) As Long
Const SEQ_NAME As String = "SEQ"
Dim nm As Name, rv As Long
On Error Resume Next
Set nm = sht.Names(SEQ_NAME)
On Error GoTo 0
'add the name if it doesn't exist
If nm Is Nothing Then
Set nm = sht.Names.Add(Name:=SEQ_NAME, RefersToR1C1:="=0")
End If
rv = Evaluate(nm.Value) + 1
nm.Value = rv
GetNextSequence = rv
End Function

UserForm taking too long to delete rows

I have been developing a UserForm that uses a listbox populated by the A column to delete specific rows based on listbox selection. But when I click the "Apply" button it takes a ridiculously long time until it processed and deleted the rows.
The code for the Apply button is the following, there is almost no other code in the UserForm. Just Me.Hide in the Cancel button.
Private Sub CommandApply_Click()
Dim i As Long
Dim n As Long
Dim col As New Collection
Dim itm As Variant
Dim rng As Range
' First, collect the row numbers corresponding to the selected items
' We work from last to first
n = Me.ListBox1.ListCount
For i = n - 1 To 0 Step -1
If Me.ListBox1.Selected(i) Then
Else
col.Add i + 1
End If
Next i
' Then delete the rows
Set rng = Worksheets("Sheet1").Range("A1:A100")
For Each itm In col
rng.Rows(itm).EntireRow.Delete
Next itm
blnCancel = False
Me.Hide
End Sub
I think you'd be better off collecting the non-selected items into a Range in your loop and then just deleting that:
Private Sub CommandApply_Click()
Dim i As Long
Dim n As Long
Dim col As New Collection
Dim itm As Variant
Dim rng As Range
' First, collect the row numbers corresponding to the selected items
' We work from last to first
n = Me.ListBox1.ListCount
For i = n - 1 To 0 Step -1
If Not Me.ListBox1.Selected(i) Then
If rng Is Nothing then
Set rng = Worksheets("Sheet1").Range("A" & i + 1)
Else
Set rng = Union(rng, Worksheets("Sheet1").Range("A" & i + 1))
End If
End If
Next i
' Then delete the rows
If not rng Is Nothing then rng.Entirerow.delete
blnCancel = False
Me.Hide
End Sub

Macro that automatically formats cell when value is entered. (convert macro to the event macro?)

I've got a spreadsheet, where I'd like A:A range to be formatted automatically so that characters will show in red and digits stay the same color. The following macro seems to work OK, but I need to manually run it every time I change value in the cell:
Sub Red_text()
Dim i As Integer
Dim MyString As String
MyString = ActiveCell.Value
For i = 1 To Len(MyString)
If IsNumeric(Mid(MyString, i, 1)) = False Then
ActiveCell.Characters(i, 1).Font.Color = RGB(247, 66, 66)
End If
Next i
End Sub
So basically I need to change it into an event macro that will reformat the current cell every time it is edited. And limit this behavior to A:A range.
Any help would be greatly appreciated!!
First a slight change to your macro:
Sub Red_text(r As Range)
Dim i As Integer
Dim MyString As String
MyString = r.Value
For i = 1 To Len(MyString)
If IsNumeric(Mid(MyString, i, 1)) = False Then
r.Characters(i, 1).Font.Color = RGB(247, 66, 66)
End If
Next i
End Sub
and also include the following event macro in the worksheet code area:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range
Set A = Range("A:A")
If Intersect(A, Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
Call Red_text(Target)
Application.EnableEvents = True
End Sub
The event macro detects entries to column A and then applies formatting.
EDIT#1:
The event macro must change to handle more than one cell at a time. Remove the original event macro and use this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim A As Range, rBIG As Range, r As Range
Set A = Range("A:A")
Set rBIG = Intersect(A, Target)
If rBIG Is Nothing Then Exit Sub
Application.EnableEvents = False
For Each r In rBIG
Call Red_text(r)
Next r
Application.EnableEvents = True
End Sub

Resources