VBA pass argument to userform - excel

I have a userform which I wish to pass a range to. I have tried a couple of different ways to do it but does not seem to work.
Here is the sub code:
Option Explicit
Sub Additional_Comments_Normal()
Dim MSG1 As Integer
Dim msg As String
Dim act As Range
On Error GoTo ErrHandler
'Calls userform
MSG1 = MsgBox("Would you like to add comments", vbYesNo, "Add comments")
If MSG1 = vbYes Then
With AddComments
On Error Resume Next
Set act = Application.InputBox(Prompt:="Please choose files you wish to add comments to", Type:=8)
If act Is Nothing Then
Exit Sub
End If
Application.ScreenUpdating = True
.Show
End With
Else
Exit Sub
End If
ErrHandler:
If Err.Number <> 0 Then
msg = "Error # " & Str(Err.Number) & " was generated by " _
& Err.Source & Chr(13) & Err.Description
MsgBox msg, , "Error", Err.HelpFile, Err.HelpContext
End If
End Sub
And the userform code is here:
Public act As Range
Private Sub CommandButton1_Click()
Dim ctl As Control
Dim rng As Range
Dim MSG2 As Integer
Dim sfile As String
If act.Column > 1 Then
MsgBox ("Please choose File name from Column 1")
Exit Sub
End If
If act.Row < 4 Then
MsgBox ("Please choose a valid file")
Exit Sub
End If
If Me.TxtComment.Value = "" Then
MsgBox "Please add comments", vbExclamation, "Additional Comments"
Me.TxtComment.SetFocus
Exit Sub
End If
If Me.TxtName.Value = "" Then
MsgBox "Please add your name", vbExclamation, "Additional Comments"
Me.TxtName.SetFocus
Exit Sub
End If
MSG1 = MsgBox("Add Comments ?", vbYesNo, "Add comments")
If MSG1 = vbNo Then
End If
If MSG1 = vbYes Then
act.Offset(0, 16).Value = act.Offset(0, 16).Text & " " & Me.TxtComment.Value
act.Offset(0, 17).Value = act.Offset(0, 17).Text & " " & Me.TxtName.Value
For Each ctl In Me.Controls
If TypeName(ctl) = "TextBox" Then
ctl.Value = ""
End If
Next ctl
AddComments.Hide
Application.DisplayAlerts = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
End If
End Sub
Private Sub CommandButton2_Click()
End Sub
Private Sub CommandButton3_Click()
Unload Me
End Sub
Private Sub UserForm_Click()
End Sub
I then get an error about the act not being defined variable.
Can anyone shed some light on better process for this?

You have set Option Explicit at the top of your code. That means that all variables need to be defined (which is considered good programming practice). So, you have two options to resolve this:
(1) Remove the line Option Explicitfrom your code or
(2) define all of your variables using the Dim command. In this case you'd have to add Dim act as Range to your Sub CommandButton1_Click on the form.
If you want to pass a variable to another sub then you can do so calling that sub with that variable like so:
Call Additional_Comments_Normal(act)
and the sub header neeeds to change like so:
Sub Additional_Comments_Normal(ByVal act as Range)
'(your code)'
End Sub
If "passing a variable to another sub" is too much trouble then you can also save the range somewhere in your file like so:
SomeHiddenSheet.Range("A1").Value2 = act
and in the other sub you can initiate act again:
act = SomeHiddenSheet.Range("A1").Value2

Related

How to call another function within a function in VBA

I'm currently trying to detect duplicated sheet name using "CheckSheet" function. And I want to call this function to run in "Add Sheet" to prevent users from creating duplicate sheet names. However, I ran into error "Compile Error: Expected function or variable" and still not succeeding in solving the problem. Kindly enlighten me where I am doing it wrong and feel free to point out if there are any weakness and better optimization to my code. Thanks in advance.
Option Explicit
Public sheetName As Variant
Public cS As Variant
Sub CheckSheet(cS) 'To check duplicate sheet name - used in AddSheet function.
Dim wS As Worksheet
Dim wsName As String
wsName = wS(sheetName)
On Error GoTo 0
If wS Is Nothing Then
cS = False
Exit Sub
End Sub
Sub AddSheet()
Dim cSheet As Variant
cSheet = CheckSheet(cS).Value
On Error Resume Next
sheetName = Application.InputBox(prompt:="New Sheet Name", Left:=(Application.Width / 2), Top:=(Application.Height / 2), Title:="Add Sheet", Type:=2)
If sheetName = "" Then
MsgBox "Sheet name cannot be empty!"
Exit Sub
ElseIf cSheet = False Then
MsgBox "Duplicate Name! Please try again!"
Exit Sub
Else
Application.ScreenUpdating = False
Sheets.Add(After:=Sheets(Sheets.Count)).Name = sheetName
MsgBox """" & sheetName & """ was successfully created!"
Sheets("Sheet1").Activate
End If
End Sub
Two things.
1. Your code can be simplified. You do not need a function to check if a worksheet exists.
Option Explicit
Sub AddSheet()
Dim sh As Object
Dim sheetName As Variant
'~~> Accept user input
sheetName = Application.InputBox(prompt:="New Sheet Name", _
Left:=(Application.Width / 2), _
Top:=(Application.Height / 2), _
Title:="Add Sheet", Type:=2)
'~~> User presses cancel
If sheetName = False Then Exit Sub
'~~> Check if the sheet name is empty
If sheetName = "" Then
MsgBox "Sheet name cannot be empty!"
Exit Sub
End If
'~~> Check if the sheet exists
On Error Resume Next
Set sh = ThisWorkbook.Sheets(sheetName)
On Error GoTo 0
If Not sh Is Nothing Then
MsgBox "Duplicate Name! Please try again!"
Exit Sub
End If
'~~> Create the worksheet
With ThisWorkbook
.Sheets.Add(After:=.Sheets(.Sheets.Count)).Name = sheetName
MsgBox """" & sheetName & """ was successfully created!"
End With
End Sub
2. Even if you want to use a function, your code has lot of errors. (One of them is pointed out by #braX above.
Is this what you are trying?
Option Explicit
Sub AddSheet()
Dim sheetName As Variant
'~~> Accept user input
sheetName = Application.InputBox(prompt:="New Sheet Name", _
Left:=(Application.Width / 2), _
Top:=(Application.Height / 2), _
Title:="Add Sheet", Type:=2)
'~~> User presses cancel
If sheetName = False Then Exit Sub
'~~> Check if the sheet name is empty
If sheetName = "" Then
MsgBox "Sheet name cannot be empty!"
Exit Sub
End If
'~~> Check if the sheet exists
If DoesSheetExists(CStr(sheetName)) = True Then
MsgBox "Duplicate Name! Please try again!"
Exit Sub
End If
'~~> Create the worksheet
With ThisWorkbook
.Sheets.Add(After:=.Sheets(.Sheets.Count)).Name = sheetName
MsgBox """" & sheetName & """ was successfully created!"
End With
End Sub
'~~> Function to check if sheet exists
Private Function DoesSheetExists(wsName As String) As Boolean
Dim sh As Object
'~~> Check if the sheet exists
On Error Resume Next
Set sh = ThisWorkbook.Sheets(wsName)
On Error GoTo 0
If Not sh Is Nothing Then DoesSheetExists = True
End Function

MsgBox & Pasting from ClipBoard

I've got 3 bits of code - trying to make them work together. On double click, if conditions met -- paste happens. If target cell is not empty, then message box with yes/no pops up. If user wants to overwrite the cell, code should do it. But, getting error in Sub Paste() on:
Target.Value = S
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Application.Intersect(Target, Range("k4:s8")) Is Nothing And Target.Value = "" Then
Cancel = True
Call Paste
'Else: MsgBox "If appropriate, clear the cell first. This is to prevent unintended data overwrite.", vbOKOnly + vbCritical, "Heads up" 'this works if MsgBox_YN_Paste won't
Else: Call MsgBox_YN_Paste
End If
End Sub
Sub Paste()
'Tools -> References -> Microsoft Forms 2.0 Object Library
'or you will get a "Compile error: user-defined type not defined"
Dim DataObj As New MSForms.DataObject
Dim S As String
DataObj.GetFromClipboard
S = DataObj.GetText
'Debug.Print S 'print code in the Intermediate box in the Macro editor
Target.Value = S
End Sub
Sub MsgBox_YN_Paste()
Dim AnswerYes As String
Dim AnswerNo As String
AnswerYes = MsgBox("Do you Wish to replace contents of this cell?", vbQuestion + vbYesNo, "Heads Up!")
If AnswerYes = vbYes Then
'Range("A1:A2").Copy Range("C1")
'ActiveCell.Clear
'or?
'Target.Value = ""
Call Paste
Else
'Range("A1:A2").Copy Range("E1")
End If
End Sub
I'd do it like this:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Application.Intersect(Target, Range("k4:s8")) Is Nothing Then Exit Sub
If Len(Target.Value) > 0 Then
If MsgBox("Do you Wish to replace contents of this cell?", _
vbQuestion + vbYesNo, "Heads Up!") <> vbYes Then Exit Sub
End If
Target.Value = ClipBoardText()
Cancel = True
End Sub
'Tools -> References -> Microsoft Forms 2.0 Object Library
Function ClipBoardText() As String
With New MSForms.DataObject
.GetFromClipboard
ClipBoardText = .GetText
End With
End Function
Target is not defined. Try
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Application.Intersect(Target, Range("k4:s8")) Is Nothing And Target.Value = "" Then
Cancel = True
Paste(Target)
'Else: MsgBox "If appropriate, clear the cell first. This is to prevent unintended data overwrite.", vbOKOnly + vbCritical, "Heads up" 'this works if MsgBox_YN_Paste won't
Else: Call MsgBox_YN_Paste
End If
End Sub
Sub Paste(ByVal Target As Range)
'Tools -> References -> Microsoft Forms 2.0 Object Library
'or you will get a "Compile error: user-defined type not defined"
Dim DataObj As New MSForms.DataObject
Dim S As String
DataObj.GetFromClipboard
S = DataObj.GetText
'Debug.Print S 'print code in the Intermediate box in the Macro editor
Target.Value = S
End Sub
I did some cleanup in the code, for clarity of understanding. Note that the return of the MsgBox is an integer numeric type and, therefore, the variable involved (AnswerYes, which I changed to AnswerYesOrNo (Integer), eliminating the other, AnswerNo, which is not used). The 'Debug.Print' actually prints on the VBE Immediate Windows.
Option Explicit
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
If Not Application.Intersect(Target, Range("k4:s8")) Is Nothing And Target.Value = "" Then
Cancel = True
Call PasteIt(Target)
'Else: MsgBox "If appropriate, clear the cell first. This is to prevent unintended data overwrite.", vbOKOnly + vbCritical, "Heads up" 'this works if MsgBox_YN_Paste won't
Else: Call MsgBox_YN_Paste(Target)
End If
End Sub
Sub PasteIt(ByRef Target As Excel.Range)
'Tools -> References -> Microsoft Forms 2.0 Object Library
'or you will get a "Compile error: user-defined type not defined"
Dim DataObj As New MSForms.DataObject
Dim S As String
DataObj.GetFromClipboard
S = DataObj.GetText
'Debug.Print S 'print code in the VBE Immediate Window
Target.Value = S
End Sub
Sub MsgBox_YN_Paste(ByRef Target As Excel.Range)
Dim AnswerYesOrNo As Integer
AnswerYesOrNo = MsgBox("Do you Wish to replace contents of this cell?", vbQuestion + vbYesNo, "Heads Up!")
If AnswerYesOrNo = vbYes Then
Call PasteIt(Target)
End If
End Sub

How to reject symbols in application.inputbox

I have some code below that creates an input box, and I don't want to allow people to type symbols in. Is there something I can do to have a msgBox popup when someone enters a symbol that says not to, or disallow them altogether? Code pasted below. Thanks for any help up front :)
If AnswerYes = vbYes Then
Dim Discount1 As Variant
Discount1 = Application.InputBox("DO NOT ENTER % SIGN", "Product Discount Percentage", "##", Type:=1)
Dim Discount2 As Variant
Discount2 = Application.InputBox("DO NOT ENTER % SIGN", "SNS Discount net Percentage", "##", Type:=1)
This should work. The False response is for cancel. It will reject any text that isn't numeric. If you wanted something more sophisticated consider using ascii code.
Sub makeInput()
makeEntry:
theResponse = Application.InputBox("DO NOT ENTER % SIGN", "Product Discount Percentage", "##", Type:=1)
If theResponse = False Then Exit Sub
Dim i As Long
For i = 1 To Len(theResponse)
If Not (IsNumeric(Mid(theResponse, i, 1))) Then
MsgBox "Invalid Entry"
GoTo makeEntry
End If
Next i
End Sub
Create a userform like this
And then paste this code in the userform code area. The TextBox1_KeyPress will ensure the user doesn't type anything other than numbers and decimal.
Option Explicit
Private Sub UserForm_Initialize()
MyValue = 0: Cancelled = False
End Sub
'~~> OK Button
Private Sub CommandButton1_Click()
If Len(Trim(TextBox1.Text)) = 0 Then
MsgBox Label1.Caption
Exit Sub
End If
MyValue = Val(TextBox1.Text)
Unload Me
End Sub
'~~> CANCEL Button
Private Sub CommandButton2_Click()
Unload Me
Cancelled = True
End Sub
Private Sub TextBox1_KeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Select Case KeyAscii
Case vbKey0 To vbKey9, vbKeyBack, vbKeyClear, vbKeyDelete, vbKeyLeft, vbKeyRight, vbKeyUp, vbKeyDown, vbKeyTab
If KeyAscii = 46 Then If InStr(1, TextBox1.Text, ".") Then KeyAscii = 0
Case Else
KeyAscii = 0
Beep
End Select
End Sub
Now you can use it like this. Paste this code in a module
Option Explicit
Public MyValue As Double
Public Cancelled As Boolean
Sub Sample()
Dim frm As New UserForm1
Dim Discount1 As Double
Dim Discount2 As Double
With frm
.Caption = "WhatEver Title"
.Label1.Caption = "Enter Product Discount Percentage"
.Show
End With
If Cancelled = False Then
Discount1 = MyValue
MsgBox Discount1
End If
Set frm = New UserForm1
With frm
.Caption = "WhatEver Title"
.Label1.Caption = "Enter SNS Discount net Percentage"
.Show
End With
If Cancelled = False Then
Discount2 = MyValue
MsgBox Discount2
End If
End Sub
EDIT
If you want, you can create a common function for the module code.
Option Explicit
Public MyValue As Double
Public Cancelled As Boolean
Sub Sample()
Dim Discount1 As Double
Dim Discount2 As Double
Discount1 = ShowInputBox("WhatEver Title", "Enter Product Discount Percentage")
If Cancelled = False Then MsgBox Discount1
Discount2 = ShowInputBox("WhatEver Title", "Enter SNS Discount net Percentage")
If Cancelled = False Then MsgBox Discount2
End Sub
Private Function ShowInputBox(Title As String, Msg As String) As Double
Dim frm As New UserForm1
With frm
.Caption = Title
.Label1.Caption = Msg
.Show
End With
If Cancelled = False Then ShowInputBox = MyValue
End Function

VBA code won't act properly through "button" but when you try step by step through F8 it works perfect

I've created macro which was working perfect in any ways. But then I needed to unprotect and then at the end of action protect it. So I added 2 sub (protect and unprotect) then I called it from main subs. When you go through it step by step (F8) it again works perfect. But when you try to click on button it act in another way (like giving message in another sheet and etc.)
Lets try fix Sub DeleteLastInformationInReyestr()
Option Explicit
Public MainID As String
Public ReyestrID As String
Public RN As Long
Public Cell As Range
Public bIsEmpty As Boolean
----------------------------------------
Sub CopyPasteFirstRow()
Call Unprotect
MainID = Sheets("Main").Range("CS7").Value
ReyestrID = Sheets("Reyestr").Cells(Rows.Count, 97).End(xlUp).Value
bIsEmpty = False
If MainID = ReyestrID Then
MsgBox "This content has been already added!", vbCritical
Else
For Each Cell In Sheets("Main").Range("E7:F7,H7:R7,T7,AA7")
If IsEmpty(Cell) = True Then
bIsEmpty = True
Exit For
End If
Next Cell
If bIsEmpty = True Then
MsgBox "Please enter all nessecery info", vbCritical
Else
Sheets("Main").Range("E7", Range("E7", "DL7")).Copy
Sheets("Reyestr").Activate
Cells(Rows.Count, 5).End(xlUp).Offset(1, 0).PasteSpecial
Cells(Rows.Count, 5).End(xlUp).PasteSpecial xlPasteValuesAndNumberFormats
End If
End If
Call Protect
End Sub
Sub DeleteLastInformationInReyestr()
Call Unprotect
MainID = Sheets("Main").Range("CS7").Value
ReyestrID = Sheets("Reyestr").Cells(Rows.Count, 97).End(xlUp).Value
RN = Worksheets("Reyestr").Cells(Rows.Count, 5).End(xlUp).Row
If MainID = ReyestrID Then
Worksheets("Reyestr").Activate
Worksheets("Reyestr").Range("E" & RN, Range("E" & RN, "DL" & RN)).Clear
Else
MsgBox "You can clear only last added content!", vbCritical
End If
Call Protect
End Sub
Sub DeleteInsertedInfoFromMain()
Call Unprotect
Dim Answer As Integer
Dim WsReyestr As Worksheet
Dim WsMain As Worksheet
MainID = Sheets("Main").Range("CS7").Value
Set WsReyestr = Worksheets("Reyestr")
Set WsMain = Worksheets("Main")
If Application.WorksheetFunction.CountIf(WsReyestr.Range("CS:CS"), WsMain.Range("CS7")) > 0 Then
Sheets("Main").Range("E7:Z7,AD7:AV7,BD7:BE7,BH7:BJ7,BO7:CM7,CQ7").ClearContents
Else
Answer = MsgBox("Yuxarida qeyd olunan melumat reyestre elave olunbayib! Melumati reyestre daxil etmeden silmek isteyirsiz?", vbQuestion + vbYesNo + vbDefaultButton2, "Diqqetli olun!")
If Answer = vbYes Then
Sheets("Main").Range("E7:Z7,AD7:AV7,BD7:BE7,BH7:BJ7,BO7:CM7,CQ7").ClearContents
Else
Exit Sub
End If
End If
Call Protect
End Sub
Sub Protect()
ThisWorkbook.Sheets("Main").Protect "PassABC", userinterfaceonly:=True
ThisWorkbook.Sheets("Main").EnableSelection = xlNoRestrictions
ThisWorkbook.Sheets("Reyestr").Protect "PassABC", userinterfaceonly:=True
ThisWorkbook.Sheets("Reyestr").EnableSelection = xlNoRestrictions
End Sub
Sub Unprotect()
ThisWorkbook.Sheets("Main").Unprotect "PassABC"
ThisWorkbook.Sheets("Reyestr").Unprotect "PassABC"
End Sub

Excel VBA End User Selecting a Chart Object

I would like to generate some code that allows an end user to select one of many charts from a sheet, after which I will do a bunch of manipulation based on that selection.
I am looking for something similar to the Application.Inputbox Type:=8 that allows for an object selection instead of a range selection.
Am I asking to much of humble old VBA??
It's a lot easier to select the chart first, then run code on the selected chart(s), than it is to pause the code and try to select the chart(s) from within the code.
But it can be done.
You need a userform, called F_ChartChooser with two buttons, btnCancel and btnContinue.
The code in the F_ChartChooser module:
Option Explicit
Private Sub btnCancel_Click()
CancelProcedure
End Sub
Private Sub btnContinue_Click()
ContinueProcedure
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
' so clicking red X doesn't crash
If CloseMode = 0 Then
Cancel = True
CancelProcedure
End If
End Sub
The code in the regular module consists of a main procedure which must get the chart(s) from the user. It has to call the userform modelessly so the user can select charts in the worksheet. This means the code continues running while the form is displayed, so the thing to do is end the sub when the userform is called.
Based on what happens with the userform, the code either continues with CancelProcedure or with ContinueProcedure. Here's the code:
Option Explicit
Dim mfrmChartChooser As F_ChartChooser
Sub Main()
' code here
' need to select chart(s) here
Application.Goto ActiveCell
Set mfrmChartChooser = New F_ChartChooser
mfrmChartChooser.Show vbModeless
End Sub
Sub CancelProcedure()
Unload mfrmChartChooser
Set mfrmChartChooser = Nothing
MsgBox "User canceled.", vbExclamation
End Sub
Sub ContinueProcedure()
Unload mfrmChartChooser
Set mfrmChartChooser = Nothing
If Not ActiveChart Is Nothing Then
' do something with active chart
' this demo is announcing that it was selected
MsgBox """" & ActiveChart.ChartTitle.Text & """ was selected.", vbExclamation
' end of demo code
ElseIf TypeName(Selection) = "DrawingObjects" Then
Dim sh As Shape
Dim vCharts As Variant
Dim nChart As Long
ReDim vCharts(0 To nChart)
For Each sh In Selection.ShapeRange
If sh.HasChart Then
' do something here with each chart
' this demo is building a list of selected charts
nChart = nChart + 1
ReDim Preserve vCharts(0 To nChart)
vCharts(nChart) = sh.Chart.ChartTitle.Text
' end of demo code
End If
Next
' this demo now is showing the list of selected charts
If nChart = 0 Then
MsgBox "No chart selected.", vbExclamation
Else
If nChart = 1 Then
MsgBox """" & vCharts(nChart) & """ was selected.", vbExclamation
Else
Dim sPrompt As String
sPrompt = nChart & " charts selected:" & vbNewLine & vbNewLine
Dim iChart As Long
For iChart = 1 To nChart
sPrompt = sPrompt & """" & vCharts(iChart) & """" & IIf(iChart < nChart, vbNewLine, "")
Next
MsgBox sPrompt, vbExclamation
End If
End If
' end of demo code
Else
' do nothing because no chart was selected
' this demo is announcing that nothing was selected
MsgBox "No chart selected.", vbExclamation
' end of demo code
End If
End Sub
The CancelProcedure and ContinueProcedure routines above have excess code in them just to help with the demo. In real code I would streamline them like this, probably not even bother to notify the user when nothing was selected (they know they canceled, right?), and just process the selected chart(s):
Sub CancelProcedure()
Unload mfrmChartChooser
Set mfrmChartChooser = Nothing
End Sub
Sub ContinueProcedure()
Unload mfrmChartChooser
Set mfrmChartChooser = Nothing
If Not ActiveChart Is Nothing Then
' do something with active chart
ProcessChart ActiveChart
ElseIf TypeName(Selection) = "DrawingObjects" Then
Dim sh As Shape
Dim vCharts As Variant
Dim nChart As Long
ReDim vCharts(0 To nChart)
For Each sh In Selection.ShapeRange
If sh.HasChart Then
' do something here with each chart
ProcessChart sh.Chart
Next
End Sub

Resources