Excel VBA Workbook_SheetChange does not work with all inputs - excel

If Target <> "" Then
A specific line of code makes an error here.
Error announcement is Type Mismatch.
I have a certain are in a sheet where user can write or paste numeric values without changing number formatting or cells formatting. I would like to enable all methods to input data into sheet. How I should fix the code?
Option Explicit
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
On Error GoTo Err_Something
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Undo
If Target <> "" Then
' user just type value in the excel sheet
Else
' user pastes data from other unknown excel sources
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
End If
Application.EnableEvents = True
Application.ScreenUpdating = True
Exit Sub
Err_Something:
MsgBox Err.Description
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
FINAL SOLUTION
I tested many different variant how to fire these events, but I never found reliable solution. Therefore I made a separate Sub, which runs every-time when user launch the program.
Sub visualMethod()
With ws
.Range(.Cells(4, 2), .Cells(22, 10)).Interior.ColorIndex = 0
.Range(.Cells(4, 2), .Cells(22, 10)).Font.Name = "Courier New"
.Range(.Cells(4, 2), .Cells(22, 10)).Font.Size = 12
.Range(.Cells(4, 2), .Cells(22, 10)).HorizontalAlignment = xlCenter
.Range(.Cells(4, 2), .Cells(22, 10)).Borders.Weight = xlThin
.Range(.Cells(4, 2), .Cells(22, 10)).NumberFormat = "0.00"
End With
End Sub

Related

Hot to solve: VBA Copy Values only 1004 Error?

I have an excel protected sheet that is being used by several users and I've found that they sometimes Copy&Paste info from a different application that Blocks the pasted cells (while changing other formats as well).
The solution that I've found is using a macro in ThisWorkbook that forces the Copy as Value.
As I don't have that much experience in VBA, I've decided to use some available code in the internet.
The problem is that the marco works just fine in my Excel, but not in an older version of Excel (where I need it to work...).
I'm getting the 1004 Error. From what I've read so far I believe this happens because the info in the Clipboard dissappears when I run the macro.
Does anyone know how to solve this?
Below is the code that I'm using:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim UndoString As String, srce As Range
On Error GoTo err_handler
UndoString = Application.CommandBars("Standard").Controls("&Undo").List(1)
If Left(UndoString, 5) <> "Paste" And UndoString <> "Auto Fill" Then
Exit Sub
End If
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Undo
If UndoString = "Auto Fill" Then
Set srce = Selection
srce.Copy
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.SendKeys "{ESC}"
Union(Target, srce).Select
Else
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
err_handler:
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Try this, two lines have been inserted
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim UndoString As String, srce As Range
On Error GoTo err_handler
UndoString = Application.CommandBars("Standard").Controls("&Undo").List(1)
If Left(UndoString, 5) <> "Paste" And UndoString <> "Auto Fill" Then
Exit Sub
End If
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Undo
If UndoString = "Auto Fill" Then
Set srce = Selection
srce.Copy
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.SendKeys "{ESC}"
Union(Target, srce).Select
Else
Selection.Copy 'This line has been inserted
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False 'This line has been inserted
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
err_handler:
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
It looks as though that section of the code (Else etc.) only applies if they used Paste but not AutoFill.
I don't see the merit of using the If block to distinguish between UndoString = "Auto Fill" and any other result, which based on an earlier If block can only include a situation where the last undoable action name started with (or was) Paste. Don't you want to do the same thing to both Auto Fill and Paste?

Trying to set text in a colum based on a match on a different sheet

I am creating a inventory control workbook and I am looking to have a column with the words "Order Placed" once I have clicked on the button to place order. I want to make sure that it is based on the right criteria. Currently in I am using a formula to place that text but once I clear the order form that goes away because its using an index match function within an if statement.
This is my current Order Placed Sub
Sub orderPlaced()
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
Dim startCell As range, lastRow As Long, lastCol As Long, ws As Worksheet
Set ws = Sheets("Re-Order List")
'This part Copies the requested information
Sheets("Re-Order List").Select
range("A1").Select
Application.CutCopyMode = False
Selection.Copy
'This part Pastes the requested information in the history
Sheets("Order History").Select
Selection.End(xlDown).Select
range("A65536").Select
Selection.End(xlUp).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteAllExceptBorders, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
'This part Copies the requested information
Sheets("Re-Order List").Select
Set startCell = range("A3")
lastRow = ws.Cells(ws.Rows.Count, startCell.Column).End(xlUp).Row
lastCol = ws.Cells(startCell.Row, ws.Columns.Count).End(xlToLeft).Column
ws.range(startCell, ws.Cells(lastRow, lastCol)).Select
Application.CutCopyMode = False
Selection.Copy
'This part Pastes the requested information in the history
Sheets("Order History").Select
Selection.End(xlDown).Select
range("A65536").Select
Selection.End(xlUp).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
'This part adds orderplaced below order form
Sheets("Order History").Select
range("A65536").Select
Selection.End(xlUp).Offset(2, 0).Select
With Selection
.Value = "Order Placed :"
.HorizontalAlignment = xlRight
End With
'This part adds the date
Sheets("Order History").Select
range("B65536").Select
Selection.End(xlUp).Offset(2, 0).Select
With Selection
.Value = Date
.NumberFormat = "dd/mmm/yyyy"
.HorizontalAlignment = xlLeft
End With
'this will set the comment in inventory to ordered
'*************************
'NEEDED CODE WILL GO HERE
'*************************
Sheets("Inventory").Select
range("K6:K400").ClearContents
'This part Clears the order form
Sheets("Re-Order List").Select
range("A4:D5000").ClearContents
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
This is my Add to Order Sub
Sub add2Order()
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
Dim C As range, rng As range, D As range, Rng1 As range, mnrng As range, acrng As range
Sheets("Inventory").Select
Set rng = range("K6:K400")
For Each C In rng
If InStr(1, C, "X") > 0 Then
'first select material number and name
Set mnrng = range(C.Offset(0, -9), C.Offset(0, -8))
Application.CutCopyMode = False
mnrng.Copy
'paste in reorder sheet
Sheets("Re-Order List").Select
Selection.End(xlDown).Select
range("A400").Select
Selection.End(xlUp).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = True
End If
Next
Sheets("Inventory").Select
Set Rng1 = range("K6:K400")
For Each D In Rng1
If InStr(1, D, "X") > 0 Then
'Second select amount and cost
Set acrng = range(D.Offset(0, -2), D.Offset(0, -1))
Application.CutCopyMode = False
acrng.Copy
'paste in reorder sheet
Sheets("Re-Order List").Select
Selection.End(xlDown).Select
range("C400").Select
Selection.End(xlUp).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = True
End If
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Code that is close is as follows
Sub test()
'
Dim w1 As Worksheet, w2 As Worksheet
Dim C As range, a As range
Set w1 = Sheets("Inventory")
Set w2 = Sheets("Re-Order List")
With w1
For Each C In .range("$A$6:$A$400")
Set a = w2.Columns(1).Find(C.Value, LookAt:=xlWhole)
If Not a Is Nothing Then
If IsEmpty(.Cells(C.Row, 1)) And .Cells(C.Row, 1).Value = w2.Cells(a.Row, 1) Then
w1.Cells(C.Row, 12).Value = "Order Placed"
End If
End If
Next C
End With
End Sub
This is the last piece to this puzzle to get it how I want it to work, I know for sure I will only have less than 400 materials to manage and if that grows I can update then but any help would be fantastic.
This is the drive link for the actual sheet
This isn't an answer until you tell us exactly where do you want to place the information
I refactored your code so you don't use select and added some pseudo code that may give you a hint
Public Sub orderPlaced()
' Turn off stuff to speed up process
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Application.ScreenUpdating = False
'This part Copies the requested information
Dim reorderListSheet As Worksheet
Set reorderListSheet = ThisWorkbook.Worksheets("Re-Order List")
reorderListSheet.Range("A1").Copy
'This part Pastes the requested information in the history
Dim orderHistorySheet As Worksheet
Set orderHistorySheet = ThisWorkbook.Worksheets("Order History")
orderHistorySheet.Cells(orderHistorySheet.Rows.Count, "A").End(xlUp).Offset(1, 0).PasteSpecial Paste:=xlPasteAllExceptBorders, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
' Find last row in reorder sheet
Dim reorderLastRow As Long
reorderLastRow = reorderListSheet.Cells(reorderListSheet.Rows.Count, "A").End(xlUp).Row
' Find last column in reorder sheet
Dim reorderLastColumn As Long
reorderLastColumn = reorderListSheet.Cells(3, reorderListSheet.Columns.Count).End(xlToLeft).Column
'This part Copies the requested information
Dim reorderStartCell As Range
Set reorderStartCell = reorderListSheet.Range("A3")
reorderListSheet.Range(reorderStartCell, reorderListSheet.Cells(reorderLastRow, reorderLastColumn)).Copy
'This part Pastes the requested information in the history
Dim orderHistoryLastRow As Long
orderHistoryLastRow = orderHistorySheet.Cells(orderHistorySheet.Rows.Count, "A").End(xlUp).Row
orderHistorySheet.Range("A" & orderHistoryLastRow).Offset(1, 0).PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'This part adds orderplaced below order form
With orderHistorySheet.Range("A" & orderHistoryLastRow).Offset(3, 0)
.Value = "Order Placed :"
.HorizontalAlignment = xlRight
End With
'This part adds the date
With orderHistorySheet.Range("B" & orderHistoryLastRow).Offset(3, 0)
.Value = Date
.NumberFormat = "dd/mmm/yyyy"
.HorizontalAlignment = xlLeft
End With
'this will set the comment in inventory to ordered
'*************************
' THIS NEXT IS PSEUDO CODE BECAUSE I COULDN'T UNDERSTAND YOUR REQUIREMENT
Dim targetCell As Range
Set targetCell = orderHistorySheet.Range("A1").Value = "=IFERROR(IF(INDEX(Table2[Material Number],MATCH(C6,Table2[Name],0)) = [#[Material Number]],""Order Placed"",""""),"""")"
' turn that into a value
targetCell.Value = targetCell.Value
'*************************
' Clear inventory sheet
Dim inventorySheet As Worksheet
Set inventorySheet = ThisWorkbook.Worksheets("Inventory")
inventorySheet.Range("K6:K400").ClearContents
'This part Clears the order form
reorderListSheet.Range("A4:D5000").ClearContents
' Turn on stuff again
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
I FIGURED IT OUT!!!!!!!!
'this will set the comment in inventory to ordered
'*************************
Dim r1 As range
Dim r2 As range
Dim cell As range
Set r1 = Sheets("Inventory").range("B6:B400")
Set r2 = Sheets("Re-Order List").range("A4:A400")
For Each cell In r1
If IsError(Application.Match(cell, r2, 0)) Then
cell.Offset(, 12) = ""
Else
cell.Offset(, 12) = "Order Placed"
' If found I need the value from Sheet2 that is in Col B of the matching row.
End If
Next cell
'*************************

How to create dynamic ranges and Improving vba Code

I Have a little bit of code (below) that I would like to improve as I find it a little clunky with regards to the ranges as they vary daily, Ideally I would like to use last row instead of using a massive range but, sadly I am not that clever :(
this is the code, if anyone fancies taking a look to improve on I would be greatly appreciative, I think the process is self explanatory (i.e auto filter and copying from one sheet to another)
Sub Refresh_click()
Set DbExtract = ThisWorkbook.Sheets("Sheet1")
Set DuplicateRecords = ThisWorkbook.Sheets("Sheet2")
Sheets("Sheet2").Unprotect
Range("A4:A50").Select
Application.CutCopyMode = False
Selection.ClearContents
Sheets("Sheet1").Select
ActiveSheet.ListObjects("Table22").Range.AutoFilter Field:=23, Criteria1:= _
"="
DbExtract.Range("F2:F99999").SpecialCells(xlCellTypeVisible).Copy
DuplicateRecords.Range("A4:A50").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("Sheet2").Protect
Sheets("Sheet1").Select
ActiveSheet.ListObjects("Table22").Range.AutoFilter Field:=23
MsgBox "Log - Updated"
End Sub
Try in this way, please:
Sub Refresh_click()
Dim DbExtract As Worksheet, DuplicateRecords As Worksheet, lastFRow As Long
Set DbExtract = ThisWorkbook.Sheets("Sheet1")
Set DuplicateRecords = ThisWorkbook.Sheets("Sheet2")
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
.CutCopyMode = False
End With
With DuplicateRecords
.Unprotect
.Range("A4:A50").ClearContents
End With
DbExtract.ListObjects("Table22").Range.AutoFilter field:=23, Criteria1:="="
lastFRow = DbExtract.Range("F" & rows.count).End(xlUp).row 'last row of F:F col
DbExtract.Range("F2:F" & lastFRow).SpecialCells(xlCellTypeVisible).Copy
DuplicateRecords.Range("A4").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
DuplicateRecords.Protect
DbExtract.ListObjects("Table22").Range.AutoFilter field:=23
With Application
.Calculation = xlCalculationAutomatic
.EnableEvents = True
.ScreenUpdating = True
End With
MsgBox "Log - Updated"
End Sub
Please, test it and send some feedback. I couldn't test it...
Copy Using AutoFilter
Adjust the values in the constants section.
The Code
Option Explicit
Sub Refresh_click()
Const srcName As String = "Sheet1"
Const srcTblName As String = "Table22"
Const srcCol As Long = 6
Const srcField As Long = 23
Const srcCrit As String = "="
Const dstName As String = "Sheet2"
Const dstFirst As String = "A4"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim cel As Range
Dim rng As Range
Dim Updated As Boolean
With wb.Worksheets(dstName)
.Unprotect
Set cel = .Range(dstFirst)
cel.Resize(cel.Worksheet.Rows.Count - cel.Row + 1).ClearContents
With wb.Worksheets(srcName).ListObjects(srcTblName)
.Range.AutoFilter
Set rng = .ListColumns(srcCol).Range _
.Resize(.ListRows.Count).Offset(1)
'Debug.Print rng.Address
.Range.AutoFilter Field:=srcField, Criteria1:=srcCrit
On Error Resume Next
rng.SpecialCells(xlCellTypeVisible).Copy
On Error GoTo 0
If Application.CutCopyMode = xlCopy Then
cel.PasteSpecial xlPasteValues
Updated = True
End If
.Range.AutoFilter
End With
.Protect
End With
If Updated Then
MsgBox "Log updated.", vbInformation, "Success"
Else
MsgBox "Log not updated.", vbCritical, "Fail"
End If
End Sub

VBA - Paste Event and undo last action of Paste

I'm trying to make my excel to Paste Values instead of PasteAll by using VBA. But I'm not able to achieve it due to failure of the line :
lastAction = Application.CommandBars("Standard").Controls("&Undo").List(1)
I'm using code as below. Could anyone kindly help me to solve this? please correct my code as well if I'm wrong.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim lastAction As String
Application.ScreenUpdating = False
Application.EnableEvents = False
lastAction = Application.CommandBars("Standard").Controls("&Undo").List(1)
If Left(lastAction, 5) = "Paste" Then
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Undo
Selection.PasteSpecial Paste:=xlPasteValues
End If
End Sub
After including above code, I understood that this doesn't helps if the content copied was not from Excel. Below code helps to handle the data from both Office Clipboard and Windows Clipboard.
Office Clipboard - Paste as Values
Windows Clipboard - Paste as text
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim UndoList As String
Application.ScreenUpdating = False
Application.EnableEvents = False
On Error GoTo Whoa
'~~> Get the undo List to capture the last action performed by user
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
'~~> Check if the last action was not a paste nor an autofill
If Left(UndoList, 5) = "Paste" Or UndoList = "Paste special" Then
Application.ScreenUpdating = True
On Error GoTo 0
'~~> Undo the paste that the user did but we are not clearing the
'~~> clipboard so the copied data is still in memory
Application.Undo
'~~> Do a pastespecial to preserve formats
'~~> Handle text data copied from a website
'Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
'Target.PasteSpecial Paste:="Text", Operation:=xlNone, SkipBlanks:=False
Call Paste_Event
On Error GoTo 0
'~~> Retain selection of the pasted data
Union(Target, Selection).Select
End If
Application.EnableEvents = True
LetsContinue:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub
Private Sub Paste_Event()
On Error GoTo Paste
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Exit Sub
On Error GoTo 0
Paste:
Selection.PasteSpecial Paste:="Text", Operation:=xlNone, SkipBlanks:=False
Exit Sub
End Sub
Here is the perfect solution which i found
Private Sub Worksheet_Change(ByVal Target As Range)
Dim UndoList As String
Application.ScreenUpdating = False
Application.EnableEvents = False
On Error GoTo Whoa
'~~> Get the undo List to capture the last action performed by user
UndoList = Application.CommandBars("Standard").Controls("&Undo").List(1)
'~~> Check if the last action was not a paste nor an autofill
If Left(UndoList, 5) = "Paste" Or UndoList = "Paste special" Then
Application.ScreenUpdating = True
On Error GoTo 0
'~~> Undo the paste that the user did but we are not clearing the
'~~> clipboard so the copied data is still in memory
Application.Undo
'~~> Do a pastespecial to preserve formats
'~~> Handle text data copied from a website
Target.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, _
SkipBlanks:=False, Transpose:=False
On Error GoTo 0
'~~> Retain selection of the pasted data
Union(Target, Selection).Select
End If
Application.EnableEvents = True
LetsContinue:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume LetsContinue
End Sub

Why am i getting this error with copy/paste?

I am getting an error
Runtime error 1004: Application defined or Object defined error
in my vba code. Could you please help me correct it?
Sub INPUT_DATA()
' INPUT_DATA Macro
' Application.ScreenUpdating = False
Application.EnableEvents = False
Sheets("Input").Select
If Range("D55").Value = 0 Then
Range("B2:AI52").Select
Selection.Copy
Sheets("Database").Select
ActiveSheet.Range("A2").End(xlDown).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
End If
Sheets("Input").Select
MsgBox ("File has been updated. DO NOT PRESS UPDATE again, as it will enter the same data once again")
End Sub
You didn't say which line is causing the error, but it looks like it's likely this line...
ActiveSheet.Range("A2").End(xlDown).Offset(1, 0).Select
It starts at A2, and then it goes down until it finds the last used row. However, if there aren't any used rows below A2, it finds the very last row in Column A. Then it tries to offset to one row below it, which doesn't exist, and hence the error. Instead, you can find the next available row as follows...
ActiveSheet.Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Select
Although, there's no need to do all of that selecting. It's very inefficient. So your macro can be re-written as follows...
Option Explicit
Sub INPUT_DATA()
' INPUT_DATA Macro
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Dim sourceWorksheet As Worksheet
Set sourceWorksheet = Worksheets("Input")
Dim destinationWorksheet As Worksheet
Set destinationWorksheet = Worksheets("Database")
With sourceWorksheet
If .Range("D55").Value = 0 Then
.Range("B2:AI52").Copy
With destinationWorksheet
.Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0).PasteSpecial Paste:=xlPasteValues
End With
End If
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
MsgBox ("File has been updated. DO NOT PRESS UPDATE again, as it will enter the same data once again")
End Sub

Resources