I want to create a VBA macro for Excel 2013 to move the cursor. I am simply re-mapping keyboard shortcuts, e.g. I want to move the cursor down using CTRL+J instead of the down arrow. So I don't need anything fancy, just a simple cursor move. I also want to do this in VBA, so I'm not looking for, e.g., an Excel plugin or add-in or anything like that.
If, under the "Developer" tab, I select "Use Relative References", and then simply record a macro moving the cursor down, I get the following, which does move the cursor:
ActiveCell.Offset(1, 0).Range("A1").Select
However, this is not a complete mimic of the down arrow. This subroutine/macro will move into hidden rows, which I do not want.
Each of the following give me an "Object doesn't support this property or method" error:
Selection.MoveDown Unit:=xlCell, Count:=1, Extend:=xlMove
...
Selection.MoveDown Unit:=xlWorksheetCell, Count:=1, Extend:=xlMove
...
Selection.MoveDown xlCell, 1, xlMove
...
Selection.MoveDown xlWorksheetCell, 1, xlMove
The following gives me a syntax error:
Selection.MoveDown(xlWorksheetCell, 1, xlMove)
I can imagine a moderatelymacro that moves the cursor, checks whether the new cell is hidden and, if so, repeats until it is not. However, I have a hard time believing that there isn't a command/method/etc that doesn't simply and easily mimic moving the cursor the way the arrow key does,ignoring hidden rows/columns. For example, I can mimic using the arrow keys while holding down the control key using, g., Selection.End(xlDown).Select. So I want to just mimic moving the cursor with an arrow key as simply as possible.
(This seems like such a simple problem that I'm guessing someone has asked/answered this before. However, if so, and if you mark my question as a duplicate, please indicate where the original answer is, as I've looked and can't find it.)
Assign your shortcut keys to:
Sub MoveDown()
Application.SendKeys "{DOWN}"
End Sub
Not sure what you're really going for, but what about this?
Option Explicit
Sub move_right()
NextVisible "Right"
End Sub
Sub move_left()
NextVisible "Left"
End Sub
Sub move_up()
NextVisible "Up"
End Sub
Sub move_down()
NextVisible "Down"
End Sub
Private Sub NextVisible(direction As String)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Dim i As Long
Dim r As Range
Set r = ActiveCell
For i = 1 To Rows.Count
On Error Resume Next 'If you're in A1 and try to go up one, it'll error. This skips that error.
Select Case direction
Case "Up"
Set r = r.Offset(-1, 0)
Case "Down"
Set r = r.Offset(1, 0)
Case "Left"
Set r = r.Offset(0, -1)
Case "Right"
Set r = r.Offset(0, 1)
Case Else
Set r = r
End Select
On Error Goto 0
If r.EntireRow.Hidden = False And r.EntireColumn.Hidden = False Then
r.Select
Exit Sub
End If
Next i
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End Sub
Then, simply assign a shortcut to each one...
So, pressing CTRL+I moves the active cell up.
This was initially written as a solution, but I've since discovered it also has problems. I'll leave it because others have already commented, but note that it is not a solution (at least not in its current form).
I found another web site here that answers my exact question. Here is the solution proposed:
Dim rng As Range
Set rng = Range( _
Cells(ActiveCell.Row + 1, ActiveCell.Column), _
Cells(Rows.Count, ActiveCell.Column) _
)
rng.SpecialCells(xlCellTypeVisible).Cells(1).Select
However, upon closer examination, this does work for moving down, but does not work for moving up. Specifically, you can move down past hidden rows, but it you try to move up past hidden rows the upward movement stops.
For Upwards:
Dim rng As Range
Set rng = Range( _
Cells(1, ActiveCell.Column), _
Cells(ActiveCell.Row - 1, ActiveCell.Column) _
)
Dim rw As Long
rw = rng.SpecialCells(xlCellTypeVisible).Areas( _
rng.SpecialCells(xlCellTypeVisible).Areas.Count _
).Row + rng.SpecialCells(xlCellTypeVisible).Areas( _
rng.SpecialCells(xlCellTypeVisible).Areas.Count).Rows.Count - 1
rng.SpecialCells(xlCellTypeVisible).Cells(rw).Select
Right:
Dim rng As Range
Set rng = Range( _
Cells(ActiveCell.Row, ActiveCell.Column + 1), _
Cells(ActiveCell.Row, Columns.Count) _
)
rng.SpecialCells(xlCellTypeVisible).Cells(1).Select
Left:
Set rng = Range( _
Cells(ActiveCell.Row, 1), _
Cells(ActiveCell.Row, ActiveCell.Column - 1) _
)
Dim rw As Long
rw = rng.SpecialCells(xlCellTypeVisible).Areas( _
rng.SpecialCells(xlCellTypeVisible).Areas.Count _
).Column + rng.SpecialCells(xlCellTypeVisible).Areas( _
rng.SpecialCells(xlCellTypeVisible).Areas.Count).Columns.Count - 1
rng.SpecialCells(xlCellTypeVisible).Cells(rw).Select
I'm going to continue to work on this, but because this is my own answer to my own question, if someone else (who knows more about VBA than I do) wants to take this solution and modify it successfully in your own answer, you have my full blessings to do so.
Note also that even for the successful downward movement this solution produces an "Overflow" error if the row number is greater than 32766.
To move one cell down, provided there is no filter applied where rows are hidden.
Selection.End(xlDown).Select
Selection.End(xlUp).Offset(1).Select
Related
I am attempting to build a loop that will look at each row in a column of data and split based on the first instance of an " ". I can get this to work on one line but the loop never activates. I tried my best at formatting this code but could not find a tutorial on how to have the commands appear as different colors and whatnot.
Dim num
Dim RowCnt As Integer
Dim x As Integer
ActiveCell.Select ' the cell to split
RowCnt = Range(Selection, Selection.End(xlDown)).Rows.Count 'determines #rows in column to split
With ActiveCell ' with block
For x = 1 To RowCnt ' define # loops
.Offset(, -1).FormulaR1C1 = "=FIND("" "",RC[1],1)" ' determine first " "
num = .Offset(, -1).Value ' assign the number of chars to 'num'
Selection.TextToColumns Destination:=ActiveCell, DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 1), Array(num, 1)), TrailingMinusNumbers:=True ' splits once based on 'num'
.Offset(, -1).ClearContents ' clear
.Offset(1, 0).Activate
Next x
End With
End Sub
I was able to cheat the answer. The issue is the Text to Columns always referred to the first cell until the sub ended. My solution was to make the looped code its own sub and call it in a separate subs loop. That way it ends the sub each time before being called again.
Use this code instead (tested: works!)
Sub updated_delimitter()
start_cell = ActiveCell.AddressLocal
n = Range(start_cell, Range(start_cell).End(xlDown)).Rows.Count 'determines #rows in column to split
Application.ScreenUpdating = False
For x = 0 To n - 1 ' define # loops
this_cell = Range(start_cell).Offset(x).AddressLocal
Range(this_cell).Select
word_ = Range(this_cell).Value
split_at = InStr(word_, " ")
Range(this_cell).TextToColumns Destination:=Range(this_cell), DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 1), Array(split_at, 1)), TrailingMinusNumbers:=True ' splits once based on 'num'
Next
Application.ScreenUpdating = True
End Sub
original code had issues with referencing in relation to 'activecell' which you referenced in the text-to-columns section - removed the with statement and no need to insert num when you can simply store it within VB (getting rid of its placements also mean no code required to remove it...
You could achieve the same in 3 lines of code♦ (w/ for loop) using the following:
Sub test2()
'Range("d2").Select
With Selection
.Offset(, 3).Formula2R1C1 = _
"=LET(x_,RC[-3]:OFFSET(RC[-3],MATCH(0,IFERROR(SEARCH("""",RC[-3]:OFFSET(RC[-3],ROWS(C[-3])-ROWS(RC[-3])-1,0)),0),0)-1,0),IF(ISODD(SEQUENCE(1,2,1,1)),MID(x_,1,IFERROR(SEARCH("" "",x_)-1,LEN(x_))),IF(ISERROR(SEARCH("" "",x_)),"""",MID(x_,SEARCH("" "",x_)+2,LEN(x_)))))"
Range(.AddressLocal, .End(xlDown).Offset(, 1)).Value = Range(Replace(.Offset(, 3).AddressLocal, "$", "") & "#").Value
.Offset(, 3).ClearContents
End With
End Sub
This uses the function:
=LET(x_,D2:OFFSET(D2,MATCH(0,IFERROR(SEARCH("",D2:OFFSET(D2,ROWS(D:D)-ROWS(D2)-1,0)),0),0)-1,0),IF(ISODD(SEQUENCE(1,2,1,1)),MID(x_,1,IFERROR(SEARCH(" ",x_)-1,LEN(x_))),IF(ISERROR(SEARCH(" ",x_)),"",MID(x_,SEARCH(" ",x_)+2,LEN(x_)))))
... which is an array function that reproduces the original list with relevant cells split as req.
REVISED
Here for sample file (requires Microsoft Onedrive a/c - read only file avail.)
♦ Office 365 compatibility; '3 lines' ignoring with/end/sub/etc.
ta 💪
Updated and Edited
I am new to this whole world, but here is my issue as it stands:
As the userform initializes the below code applies a filter to my 'clean import', copies column a into a temp sheet, which is what the listbox uses to populate itself.
Set ws = ThisWorkbook.Worksheets("Clean_Import")
ws.Activate
On Error Resume Next
ws.ShowAllData
On Error GoTo 0
ws.Range("A1:K1000").AutoFilter Field:=5, Criteria1:="<1"
Range("A:A").Select
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Sheets("TempSheet").Select
Columns("A:A").Select
Range("A2").Activate
ActiveSheet.Paste
ODList1.List = Sheets("TempSheet").Range("A2:A100").Value
End Sub
From then in it is just double clicking on the list box to lookup the selected items and vlook some data into text boxes.
With Me.ODList1
For i = 0 To .ListCount - 1
If .Selected(i) Then
TextBox11.Value = Application.VLookup(.List(i, 0), Sheet3.Range("A1:K100"), 3)
TextBox12.Value = Format(Application.VLookup(.List(i, 0), Sheet3.Range("A1:K100"), 7), "dd / mm / yyyy")
TextBox13.Value = Application.VLookup(.List(i, 0), Sheet3.Range("A1:K100"), 10)
Exit For
End If
Next
End With
This code works in all but one of my scenarios. I realise now if i select the list box item that happens to be the first line in the range OR the last in the range in my 'clean import' then I get the following error.
Run-Time error '-2147352571 (80020005)': Could not set the value
property. Type mismatch.
The only thing I can think is that the value doesn't match, but that doesn't seem possible as the list box is populated from a direct copy from the range it is vlooking through
I look forward to hearing your thoughts,
Cheers,
Bill
That is happening because the Vlookup is not able to find a match. Here is a simple way to reproduce the error
Private Sub CommandButton1_Click()
TextBox1.Value = Application.VLookup("Sid", Sheet1.Range("A1:K100"), 3)
End Sub
To handle this, you need to introduce proper error handling. Here is an example
Dim Ret As Variant
Ret = Application.VLookup("Sid", Sheet1.Range("A1:K100"), 3)
If IsError(Ret) Then
TextBox1.Value = "Error"
Else
TextBox1.Value = Ret
End If
I made some small adjustments to the way I brought the data in and problem went away.
Not really sure where the issue was creeping in. But it crept back out again.
Excel workbook consist of 10,000 rows and 25 columns and take 15 mins to complete this process. i need to reduce the runtime to complete this process into less than 1 min. kindly help me out from this situtaion.
For Each cl In rng.SpecialCells(2)
For i = Len(cl.Value) To 1 Step -1
If cl.Characters(i, 1).Font.Strikethrough Then
cl.Characters(i, 1).Delete
End If
Next i
Next cl
Very fast approach via xlRangeValueXMLSpreadsheet Value
Using the relatively unknown xlRangeValueXMLSpreadsheet Value, also referred to as ►.Value(11) solves the question by a very simple string replacement (though the xml string handling can reveal to be very complicated under special conditions).
This approach (quickly tested for 10000 rows) seems to be up to 90 times faster as Tim's valid solution refining the original code, but lasting 14 minutes :-)
Sub RemoveStrThr(rng As Range, Optional colOffset As Long = 1)
'a) Get range data as xml spreadsheet value
Dim xmls As String: xmls = rng.Value(xlRangeValueXMLSpreadsheet) ' //alternatively: xmls = rng.Value(11)
'b) find start position of body
Dim pos As Long: pos = InStr(xmls, "<Worksheet ")
'c) define xml spreadsheet parts and remove <S>-node sections in body
Dim head As String: head = Left(xmls, pos - 1)
Dim body As String: body = Mid(xmls, pos)
'remove strike throughs
Dim results: results = Split(Replace(body, "</S>", "^<S>"), "<S>")
results = Filter(results, "^", False) ' negative filtering of special char "^"
body = Join(results, "")
'd) write cleaned range back
rng.Offset(0, colOffset).Value(11) = head & body
End Sub
Example call
Sub TestRemove()
Application.ScreenUpdating = False
Dim t As Double
t = Timer
RemoveStrThr Sheet1.Range("A2:Z10000"), 27 ' << change to your needs
Debug.Print "done", Format(Timer - t, "0.00 secs")
Application.ScreenUpdating = True
End Sub
Help reference links
Excel XlRangeValueDataType enumeration
Excel Range Value
Addendum (due to #Tim' valuable comment)
Note that if the whole cell content should be struck out then this will not remove the struck-out content: there are no <S> or </S> tags in the element, since the strikethrough is applied via a Style rule (via the xml spreadsheet value head).
To meet this eventuality
"...you could add a second step using something like Application.FindFormat.Font.Strikethrough = True: rng.Replace What:="*", Replacement:="", LookAt:=xlPart, SearchOrder:=xlByRows, SearchFormat:=True: Application.FindFormat.Clear to take care of those cells."
Any use of the Characters collection tends to be kind of slow, so the best you can do (beyond turning off screenupdating) is get some minor improvements by (eg) ignoring cells with no strikethrough, checking for cases where all content is struck through, and batching your calls to Delete.
Sub tester()
Dim t
Range("C1:C3").Copy Range("A1:A999") 'creating some dummy cell values (no/mixed/all ST)
Application.ScreenUpdating = False
t = Timer
RemoveStrikeThrough Range("A1:A999")
Debug.Print "done", Timer - t
End Sub
Sub RemoveStrikeThrough(rng As Range)
Dim cl As Range, hasST, i As Long, pos As Long, st As Boolean
For Each cl In rng.Cells
'only process cells which have any strikethrough style applied
' hasST will be False (no ST), True (all ST) or Null (mixed ST)
hasST = cl.Font.Strikethrough
If TypeName(hasST) = "Boolean" Then
If hasST Then
cl.ClearContents 'all text is struck out, so clear the cell
Else
'Debug.Print "No strikethrough", cl.Address
End If
Else
'mixed - do char by char
For i = Len(cl.Value) To 1 Step -1
If cl.Characters(i, 1).Font.Strikethrough Then
If Not st Then 'new run?
st = True
pos = i
End If
Else
If st Then 'previous run?
cl.Characters(i + 1, pos - i).Delete
st = False
End If
End If
Next i
'remove last strikethough if any
If st Then cl.Characters(1, pos).Delete
st = False 'reset this
End If
Next cl
End Sub
I already have a barcode scanner VBA function, that recognizes the barcode number, but the problem I have is that I have to click enter every time, is there any way to do it automatically and store the count in a certain column? Currently it works if I enter the same value stored in column B, it will count the records in column C, but I want to avoid hitting enter every time
This is what I got so far
Private Sub btnAdd_Click()
Dim TargetCell As Range
If WorksheetFunction.CountIf(Sheets("Sheet1").Columns(2), TextBox1.Value) = 1 Then
Set TargetCell = Sheets("Sheet1").Columns(2).Find(TextBox1.Value, , xlValues, xlWhole).Offset(0, 1)
TargetCell.Value = TargetCell.Value + 1
Else
MsgBox "Code not found"
End If
Me.Hide
End Sub
It's hard to say what you have. For example, who presses the button? Or, does your scanner enter a return. I think the code below should work under any circumstances. Please try it.
Private Sub TextBox1_Change()
Dim TargetCell As Range
Dim Qty As Long
With TextBox1
If Len(.Value) = 3 Then
Set TargetCell = Worksheets("Sheet1").Columns(2) _
.Find(.Value, , xlValues, xlWhole)
If TargetCell Is Nothing Then
MsgBox """" & .Value & """ Code not found"
Else
With TargetCell.Offset(0, 1)
Qty = .Value + 1
.Value = Qty
End With
Application.EnableEvents = False
TextBox1.Value = "Count = " & Qty
Application.EnableEvents = True
End If
.SelStart = 0
.SelLength = Len(.Value)
End If
End With
End Sub
I think you have a user form and in this form you have a text box called TextBox1. If so, the code should be in the user form's code module. If you have a text box in your worksheet paste the code to the code module of the sheet on which the text box resides.
Now, you need to adjust this line of code If Len(.Value) = 3 Then to determine when to process the data. This is because the Change event will occur whenever even a single character is entered. I tested with 3 characters. Change the number to a value equal to the length of the numbers you scan in. In theory that still leaves the CR hanging which your scanner might also send. If that causes a problem experiment with >= in place of the = in my code.
The code will add the scan to the existing quantity, just as you had it, and indicate the new total in the text box, in case you are interested. You might replace this with "OK". The code will select the text it enters. Therefore when you enter something else, such as a new scan, it will be over-written without extra clicks being required.
This should be an easy question for the seasoned pros.
1) I'm trying to offset the active cell down one each iteration of the loop.
2) I can only move down by one because I'm not sure of the syntax available.
3) I was thinking the_result = the_result + 1 but that doesn't work :(
Sub vlookupTest()
search = Worksheets("Sheet1").Range("B2")
For i = 2 To 5
the_result = Application.WorksheetFunction.vlookup(search, Worksheets("Sheet1").Range("F2:G5"), 2, True)
MsgBox the_result
search = ActiveCell.Offset(1, 0)
Next i
End Sub
I can see why the loop only moves down two cells and gets stuck since offset only moves down one from "B2" but not sure of the correct syntax to continually move down in this instance
Your code does not explicitly force the ActiveCell to start at Worksheets("Sheet1").Range("B2") but you subsequently offset from the ActiveCell. This seems like a mashup of methods to assign a value into search. It's probably best to avoid relying on ActiveCell altogether.
Sub vlookupTest()
dim searchTerm as variant, the_result as variant
searchTerm = Worksheets("Sheet1").Range("B2").value
For i = 2 To 5
the_result = Application.WorksheetFunction.vlookup(searchTerm , Worksheets("Sheet1").Range("F2:G5"), 2, True)
MsgBox the_result
searchTerm = Worksheets("Sheet1").Range("B2").Offset(i - 1, 0).value
Next i
End Sub
Just as an FYI, the use of True as the range_lookup parameter in VLOOKUP returns an approximate match and F2:F5 must be sorted in an ascending manner.
The ActiveCell never changes in your code.
Replace
search = ActiveCell.Offset(1, 0)
with
ActiveCell.Offset(1, 0).Select
search = ActiveCell