I have built an Excel Macro (as an Excel Addin) to randomly generate numbers.
I built it because the rand() function on excel keep generating new numbers at each action in the Excel file. So I tried to build something that "freeze" the formula once it has generated a number.
It works pretty well but when I close the file and reopen it, the numbers change.
How can I fix that ?
I have tried something like : If current cell = Blank ==> generate, otherwise exit function. But it doesn't work.
Here is the code I'm using :
Function RandomFreeze()
Static AlreadyRandomized As Boolean
AlreadyRandomized = False
Static Low As Double
Static High As Double
Low = 1
High = 100000000
If Worksheets("Sheet1").Range("A1") = "" Then
If AlreadyRandomized = False Then
RandomFreeze = Int(Rnd * (High + 1 - Low)) + Low
AlreadyRandomized = True
End If
Else
MsgBox "Erreur"
AlreadyRandomized = True
End If
End Function
Any help with this issue will be appreciated.
Thanks in advance
You can store the value of AlreadyRandomized variable to a CustomDocumentProperty and read/set its value accordingly.
Public Sub T()
Dim p As Object
Set p = CustomPropertyByName("AlreadyRandomized")
If Not CBool(p.Value) Then 'Randomize
p.Value = True 'Randomized
End Sub
Two helper functions, one creates the property if it doesn't exist and returns a reference to it and the second simply checks if the property exists.
'CustomPropertyByName()
Public Function CustomPropertyByName(ByVal propertyName As String) As Object
If Not PropertyExists(propertyName) Then
ThisWorkbook.CustomDocumentProperties.Add Name:=propertyName, _
LinkToContent:=False, _
Type:=msoPropertyTypeNumber, _
Value:=0
End If
Set CustomPropertyByName = ThisWorkbook.CustomDocumentProperties(propertyName)
End Function
'PropertyExists()
Private Function PropertyExists(ByVal propertyName As String) As Boolean
Dim p As Object
For Each p In ThisWorkbook.CustomDocumentProperties
If p.Name = propertyName Then
PropertyExists = True
Exit Function
End If
Next p
End Function
Note: A runtime error occurs if you try to access a CustomDocumentProperty that doesn't exist.
Related
I am trying to write some intermediate results in a user defined VBA function to a worksheet. I have tested the function, and it works correctly. I am aware that I cannot modify / write to cells from a UDF, so I tried passing the relevant results to a subroutine which I hoped would then be able to write to the spreadsheet.
Unfortunately my scheme doesn't work, and I am trying to think through this problem.
Public Function f(param1, param2)
result = param1 * param2
call writeToSheet(result)
f = param1 + param2
end
public sub writeToSheet(x)
dim c as range
c = range("A1")
c.value = x
end
I would like to see the product of param1 and param2 in cell A1. Unfortunately, it does not happen - the subroutine just ends abruptly as soon as it attempts to execute the first statement (c = range("A1") ). What am I doing wrong, and how can I fix it?
If it is simply impossible to write to a spreadsheet in this way, is there some other way in which to store intermediate results for later review? My real life problem is a little more complicated that my stylized version above, as I generate a new set of intermediate results each time I go through a loop, and want to store them all for review.
This idea might work for you. The function ParamProduct calls SetProps which writes both parameters to custom document properties (View from File > Properties > Advanced Properties > Custom). Call the function with =ParamProduct(A1, A2) or =ParamProduct(123, 321)
Function ParamProduct(Param1 As Variant, _
Param2 As Variant) As Double
Dim Fun As Double
Dim Param As Variant
Dim i As Integer
Param = Param1
For i = 1 To 2
SetProp "Param" & i, Param
Param = Param2
Next i
ParamProduct = Param1 + Param2
End Function
Private Sub SetProp(Pname As String, _
PropVal As Variant)
' assign PropVal to document Property(Pname)
' create a custom property if it doesn't exist
Dim Pp As DocumentProperty
Dim Typ As MsoDocProperties
If IsNumeric(PropVal) Then
Typ = msoPropertyTypeNumber
Else
Select Case VarType(PropVal)
Case vbDate
Typ = msoPropertyTypeDate
Case vbBoolean
Typ = msoPropertyTypeBoolean
Case Else
Typ = msoPropertyTypeString
End Select
End If
On Error Resume Next
With ThisWorkbook
Set Pp = .CustomDocumentProperties(Pname)
If Err.Number Then
.CustomDocumentProperties.Add Name:=Pname, LinkToContent:=False, _
Type:=Typ, Value:=PropVal
Else
With Pp
If .Type <> Typ Then .Type = Typ
.Value = PropVal
End With
End If
End With
End Sub
Use this UDF to recall the properties to the worksheet.
Function GetParam(ByVal Param As String) As Variant
GetParam = Propty(Param)
End Function
Private Function Propty(Pname As String) As Variant
' SSY 050 ++
' return null string if property doesn't exist
Dim Fun As Variant
Dim Pp As DocumentProperty
On Error Resume Next
Set Pp = ThisWorkbook.CustomDocumentProperties(Pname)
If Err.Number = 0 Then
Select Case Pp.Type
Case msoPropertyTypeNumber
Fun = CLng(Fun)
Case msoPropertyTypeDate
Fun = CDate(Fun)
Case msoPropertyTypeBoolean
Fun = CBool(Fun)
Case Else
Fun = CStr(Fun)
End Select
Fun = Pp.Value
End If
The worksheet function below works (A6 has a value of "Param2")
=GetParam("Param1")*GetParam(A6)
The above code will create a property if it doesn't exist or change its value if it does. The sub below will delete an existing property and do nothing if it's called to delete a property that doesn't exist. You might call it from one of the above subs or functions.
Private Sub DelProp(ByVal Pname As String)
On Error Resume Next
ThisWorkbook.CustomDocumentProperties(Pname).Delete
Err.Clear
End Sub
Thanks a mill everyone. Printing to the immediate window is the easiest by far, but allowed me to print only a single item. I therefore concatenated all 5 items into a single string and printed it in the immediate window:
dummystr = CStr(slope1) & ", " & CStr(intercept1) & ", " & CStr(slope2) & ", " & CStr(intercept2) & ", " & CStr(sse(i))
Debug.Print dummystr
Task:
I work in Excel2013. I tried to write in VBA a userform to add parameters into dynamic named ranges. All named ranges are held in one sheet and were created using insert>table. I select the range, show existing values and get the new value. All went well untill I actually got to adding value to the range.
Problem:
Excel shuts down most of the time when I try to run the UserForm. Saying:
"Run-time error '-2147417848 (80010108)' Method X of object 'Range' failed"
with different methods ('_Default' last time I checked) at different stages of me breaking code down.
Symtoms:
After this line as I found I get the error:
Cells(y, x) = v
where y and x are integers and v a string I get from the userform. During the debug I checked all values are defined and have values. Moreover, Immediate window with the same numbers input manually (not as variables), works!
It mostly doesn't work, though it did follow through doing the job.
If somone could tell the reason why it breaks it would be greatly appreciated!
Some of the captions and potential values are in Unicode in case it matters, though I tried putting it all in English as well.
Private Sub UserForm_Initialize()
' Preparing all controls of UserForm
Sheet2.Activate
Me.LB_parameter.SetFocus
Me.LB_parameter.value = ""
Me.LB_elements.RowSource = ""
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
End Sub
Private Sub LB_parameter_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
' Filling the existing list of values for the selected parametr
If Me.LB_parameter.value <> "" Then
Me.LB_elements.RowSource = "D_" & Me.LB_parameter.value & "s"
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
End If
End Sub
Private Sub TB_element_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
' Catching the event of filling out the potential new value
Me.Btn_Add.Enabled = True
Me.Btn_Add.Locked = False
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
End Sub
Private Sub Btn_Add_Click()
If Me.TB_element.Text = "" Then
' Check if Empty
MsgBox ("Âû íå âïèñàëè çíà÷åíèå!")
' Reset the UserForm
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
Else
' check if exists
Dim str
For Each str In range("D_" & Me.LB_parameter.value & "s")
If Me.TB_element.Text = str Then
MsgBox ("Ââåäåííîå çíà÷åíèå óæå ñóùåñòâóåò!")
' reset the UserForm
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
Me.L_element.Enabled = True
Me.TB_element.Enabled = True
Me.TB_element.Locked = False
Me.TB_element.SetFocus
Me.TB_element.value = ""
Exit Sub
End If
Next str
' add to the range here
Dim x As Integer, y As Integer, v As String
y = range("D_" & Me.LB_parameter.value & "s").Rows.Count + 2
x = Me.LB_parameter.ListIndex + 1
v = Me.TB_element.value
' Next line causes break down
Cells(y, x) = v
MsgBox ("Âû äîáàâèëè ýëåìåíò:'" & v & "' äëÿ ïàðàìåòðà '" & Me.LB_parameter.value & "'.")
' Reset the Userform
Me.LB_parameter.SetFocus
Me.LB_parameter.value = ""
Me.LB_elements.RowSource = ""
Me.L_element.Enabled = False
Me.TB_element.Enabled = False
Me.TB_element.Locked = True
Me.Btn_Add.Enabled = False
Me.Btn_Add.Locked = True
End If
End Sub
Sheet I add values to the parametrs and namedranges window:
The UserForm layout:
Cells(y, x) = v
This call is shorthand for this:
ActiveSheet.Cells(y, x).Value = v
I'm not sure why it's crashing on you, but the _Default property of a Range object being its Value, what I'd try here is being more explicit about what I'm trying to achieve, namely:
Exactly which Worksheet is supposed to get modified?
Exactly which Range is being referred to?
I very very very seldom work with ActiveSheet - most of the time I know exactly what object I'm working with. Try using an object. You can create a new one:
Dim target As Worksheet
Set target = ThisWorkbook.Worksheets("pl")
...Or you can give the sheet a code name in the properties toolwindow (F4):
That (Name) property defines an identifier that you can use in VBA code to access a global-scope object that represents that specific worksheet. Assuming that's Sheet1, you could do this:
Sheet1.Cells(x, y) = v
If that still fails, then you can be even more specific about the Range object you're accessing and the property you're setting:
Dim target As Range
Set target = Sheet1.Cells(x, y)
target.Value = v
Normally that wouldn't make a difference though. But I see you're making Range calls, which are also implicitly calling into the ActiveSheet.
I'd start by eliminating these, and working off an explicit object reference.
Then I'd work on getting the spreadsheet logic out of the form; that button click handler is doing way too many things - but I digress into Code Review territory - feel free to post your code there when you get it to work as intended!
Looks like the problem lies in my version of Excel. Not sure if the problem is in my copy or in the 2013 in general. In Excel 2007 on the same machine the UserForm with given suggestions worked continuously without any errors at all! Will update in comments later as I try it in different versions.
Simple Layup for anyone. I've written a function isMac() which looks at the current operating System, if the first 3 letters are Mac, it is supposed to return True. I then want to assign the return value of the function to a variable in another function. I'm currently running a Mac, so when I run the following code, the MsgBox says True, while the Debug.Print returns False
Function isMac() As Boolean
If Left(Application.OperatingSystem, 3) = "Mac" Then
result = True
Else
result = False
End If
MsgBox result
End Function
Sub test()
Debug.Print isMac()
End Sub
How do I correctly return a Boolean from my IF statement so that I can utilize it in another function?
Try assigning the result to the function.
Function isMac() As Boolean
isMac = CBool(LCase(Left(Application.OperatingSystem, 3)) = "mac")
End Function
Sub test()
Debug.Print isMac()
End Sub
Another approach would be Compiler Directives and Compiler Constants. In a module code sheet's declarations area as,
#If Mac Then
Public Const bMAC as Boolean = True
#Else
Public Const bMAC as Boolean = False
#End If
Use bMAC anywhere in your code to determine whether the OS is a Mac or not.
I have the following calcScores function written:
Function calcScores(category As String) As Integer
Dim count As Integer
count = 0
For Each Ctl In UserForm1.Controls
If Ctl.Tag = category And TypeName(Ctl) = "CheckBox" Then
Dim box As MSForms.CheckBox
Set box = Ctl
If box.Value = True Then
count = count + 1
End If
End If
Next
calcScores = count
End Function
This function takes a tag named "category" as a string and then checks the form for all check boxes with that tag and counts the ones that are checked. I know it works and counts the right number, because I have slightly edited it to output it's value to a label on the form instead of returning it.
When I try to call it in another function like this:
Function sortScores()
Dim scores(0 to 5) as Integer
scores(0) = calcScores "rChk"
**CODE CONTINUES**
End Function
I get an error that says "Expected: End of Statement" as soon as I leave the line that assigns the function's return to scores(0). calcScores is assigned before sortScores, and was succesfully called in a sub before using the same syntax.
Any idea what the error could be?
Call you function like this
scores(0) = calcScores("rChk")
Functions are called like that. Subs are called by
subName argument
I have the following function in VBA:
Private Function Option1Checked(option1 As OptionButton) As Integer
option1.ForeColor = vbGreen
If (option1.Value = True) Then
Option1Checked = 1
End If
Option1Checked = 0
End Function
Whenever I try to call the function like this
counter = counter + Option1Checked(OptionButton1)
I get a type mismatch error at runtime. But OptionButton1 is OptionButton, so what am I doing wrong?
You're running into one of the 'features' of VBA here. If you refer to some objects, like the option button, without a property specified, VBA assumes you want the default property, not the object itself. In the case of the option button, the default property is .Value, so in your code, OptionButton1 is not the option button object, but rather TRUE or FALSE depending on whether or not the OptionButton1 is checked.
Your best bet will be to change your function to this:
Private Function Option1Checked(option1 As Boolean) As Integer
//option1.ForeColor = vbGreen
If (option1 = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
The downside here is that you cannot change the foreground color of the option button to green without referring to it by name.
An alternative that would get you the functionality that you want would be to pass the name of the option button to your Function.
Private Function Option1Checked(ByVal option1 As String) As Integer
UserForm1.Controls(option1).ForeColor = vbGreen
If (UserForm1.Controls(option1) = True) Then
Option1Checked = 1
Else
Option1Checked = 0
End If
End Function
Sub MyCountingRoutine()
Dim str As String
str = OptionButton1.Name
counter = counter + Option1Checked(str)
End Sub
Make sure you include the Else in the If..Then statement in your function, otherwise you will always get 0 back.