Consider the following intuitive VBA command, which selects the first 10 slides of a PPT presentation:
ActivePresentation.Slides.Range(Array(1,2,3,4,5,6,7,8,9,10)).Select
I would like to express this in more succinct form. For example
-- psuedo-code
ActivePresentation.Slides.Range(CreateRange(1,10)).Select
How can you do this with the VBA available in Powerponit?
No such build-in function in VBA, but it's quite simple to create your own:
Function createRange(fromVal As Long, toVal As Long) As Long()
ReDim a(fromVal To toVal) As Long
Dim i As Long
For i = fromVal To toVal: a(i) = i: Next
createRange = a
End Function
... and voilà, your pseudo-code is no longer a pseudo-code
Related
I'm writing a UDF in which I'm using an aray that is filled from another workbook.
It works, but, it's rather slow.
I'm thinking that's because every instance of the UDF (20000+ lines) calls to the other workbook to fill the array.
Below is the function. What I'm asking is: can the sourceCategorie be filled by a sub globally, so nog every line has to call on the other workbook. Could this improve performance?
public Function categorieoppunten(punten As Double) As Integer
Dim sourceCategorie As Variant
Dim categorie, i As Integer
sourceCategorie = Workbooks("Brontabellen.xlsx").Worksheets("Bron").range("C46:C68").Value
For i = 23 To 1 Step -1
If punten < sourceCategorie(i, 1) Then
categorie = i
End If
Next i
categorieoppunten = categorie
End Function
I'm trying to use Application.Caller inside a Function (code below), but Excel returns a #VALUE and the background color is not set.
The personal function is called from an Excel cell. The idea is to map RGB values to color display in a "synchronous" fashion (i.e. without having to press a button).
When I run the following function through the debugger and step just before the instruction vCaller.Interior.Color = RGB(rlev, glev, blev), I can manually set the background color to green by pasting the exact same instruction in the execution console. So I'm puzzled as to why Excel is failing but VBA isn't.
Any clue ?
Public Function RGB_print(rlev As Integer, glev As Integer, blev As Integer)
As String
Dim vCaller As Variant
Set vCaller = Application.Caller
If TypeName(vCaller) = "Range" Then
vCaller.Interior.Color = RGB(rlev, glev, blev)
End If
RGB_print = ""
End Function
I completely agree with the comment from #Rory - I'd never use this code in my own projects, but I wanted to see anyway....
If in a normal module you create this function:
Public Function RGB_print(rlev As Integer, glev As Integer, blev As Integer)
Application.Volatile
End Function
Then in your sheet add this code:
Private Sub Worksheet_Calculate()
Dim rFormula As Range
Dim vForm As Variant
Dim sArguments As String
Dim sFormula As String
Dim rgblev As Variant
Set rFormula = Sheet1.Cells.SpecialCells(xlCellTypeFormulas)
For Each vForm In rFormula
If InStr(vForm.FormulaLocal, "RGB_print") <> 0 Then
sFormula = vForm.FormulaLocal
sArguments = Mid(sFormula, InStr(sFormula, "(") + 1, InStr(sFormula, ")") - InStr(sFormula, "(") - 1)
rgblev = Split(sArguments, ",")
vForm.Interior.Color = RGB(Evaluate(rgblev(0)), Evaluate(rgblev(1)), Evaluate(rgblev(2)))
End If
Next vForm
End Sub
This worked for formula such as:
=RGB_print(255,0,255) and =RGB_print(A5,B5,C5)
But again, find another way - this code has so many pitfalls I'll probably lose 100 reputation just for posting it.
Ok, as an alternative to Darrent's very precise reply, I'm reposting Tim Williwam's comment : whether one may/should mix functions and macros is an important question and it has been discussed here. Bottom line is : you can but don't do it unless you know what you are doing and are prepared to face the consequences.
Looking over vba arrays and stumbled upon something and need someone to clear it up.
Sub AAATest()
Dim StaticArray(1 To 3) As Long
Dim N As Long
StaticArray(1) = 1
StaticArray(2) = 2
StaticArray(3) = 3
PopulatePassedArray Arr:=StaticArray
For N = LBound(StaticArray) To UBound(StaticArray)
Debug.Print StaticArray(N)
Next N
End Sub
AND
Sub PopulatePassedArray(ByRef Arr() As Long)
''''''''''''''''''''''''''''''''''''
' PopulatePassedArray
' This puts some values in Arr.
''''''''''''''''''''''''''''''''''''
Dim N As Long
For N = LBound(Arr) To UBound(Arr)
Arr(N) = N * 10
Next N
End Sub
What's happening at
PopulatePassedArray Arr:=StaticArray
in AAATest sub
There are two ways you can pass arguments to another procedure: using named arguments or in order. When you pass them in order, you must past them in the same order as the procedure definition.
Function DoTheThing(arg1 As Double, arg2 As String, arg3 As Boolean) As Double
When you call this function (in order), you call it like
x = DoTheThing(.01, "SomeString", TRUE)
When you call the function using named arguments, you use :=, the name of the argument, and the value of the argument. The := is not a special assignment operator - well I guess it kind of is. The upshot is that when you use named arguments, you can supply them in any order.
x = DoTheThing(arg2:="SomeString", arg3:=TRUE, arg1:=.01)
Some people also think that named arguments make your code more readable. I'm not one of those people. It clutters it up and if you're passing more than two or three arguments, you're doing it wrong anyway.
I have a function in VBA of the type
Function MyFunc(Indx As Integer, k As Long, Rho As Range, A As Range) As Variant
....
End Function
which is called as a user-defined function from within the Excel worksheet. When called with the last two arguments being a range
Result = MyFunc(1,98,A1:A2, B1:B2))
it works fine. However, when I try to directly use an array constant instead of a range
Result = MyFunc(1,98,{10,11}, {20,30})
it returns a #VALUE error.
I thought I could fix it by redefining the last two arguments as arrays of type double, but this didn't work either
Function MyFunc(Indx As Integer, k As Long, Rho() As Double, A() As Double) As Variant
....
End Function
Does someone have a suggestion for a flexible solution, which would permit either calling method: by range, as well as by an array constant?
You could declare your two parameters as Variant types, then in your function check what has been passed to them using the TypeName(varname) function.
Maybe you could convert your array in a range object while calling the function.
Result = MyFunc(1,98,Range(10,11), Range(20,30))
VBA for Access lacks a simple Max(x,y) function to find the mathematical maximum of two or more values. I'm accustomed to having such a function already in the base API coming from other languages such as perl/php/ruby/python etc.
I know it can be done: IIf(x > y, x,y). Are there any other solutions available?
I'll interpret the question to read:
How does one implement a function in Access that returns the Max/Min of an array of numbers? Here's the code I use (named "iMax" by analogy with IIf, i.e., "Immediate If"/"Immediate Max"):
Public Function iMax(ParamArray p()) As Variant
' Idea from Trevor Best in Usenet MessageID rib5dv45ko62adf2v0d1cot4kiu5t8mbdp#4ax.com
Dim i As Long
Dim v As Variant
v = p(LBound(p))
For i = LBound(p) + 1 To UBound(p)
If v < p(i) Then
v = p(i)
End If
Next
iMax = v
End Function
Public Function iMin(ParamArray p()) As Variant
' Idea from Trevor Best in Usenet MessageID rib5dv45ko62adf2v0d1cot4kiu5t8mbdp#4ax.com
Dim i As Long
Dim v As Variant
v = p(LBound(p))
For i = LBound(p) + 1 To UBound(p)
If v > p(i) Then
v = p(i)
End If
Next
iMin = v
End Function
As to why Access wouldn't implement it, it's not a very common thing to need, seems to me. It's not very "databasy", either. You've already got all the functions you need for finding Max/Min across domain and in sets of rows. It's also not very hard to implement, or to just code as a one-time comparison when you need it.
Maybe the above will help somebody.
Calling Excel VBA Functions from MS Access VBA
If you add a reference to Excel (Tools → References → Microsoft Excel x.xx Object Library) then you can use WorksheetFunction to call most Excel worksheet functions, including MAX (which can also be used on arrays).
Examples:
MsgBox WorksheetFunction.Max(42, 1999, 888)
or,
Dim arr(1 To 3) As Long
arr(1) = 42
arr(2) = 1999
arr(3) = 888
MsgBox WorksheetFunction.Max(arr)
The first call takes a second to respond (actually 1.1sec for me), but subsequent calls are much more reasonable (<0.002sec each for me).
Referring to Excel as an object
If you're using a lot of Excel functions in your procedure, you may be able to improve performance further by using an Application object to refer directly to Excel.
For example, this procedure iterates a set of records, repeatedly using Excel's MAX on a Byte Array to determine the "highest" ASCII character of each record.
Option Compare Text
Option Explicit
'requires reference to "Microsoft Excel x.xx Object Library"
Public excel As New excel.Application
Sub demo_ListMaxChars()
'list the character with the highest ASCII code for each of the first 100 records
Dim rs As Recordset, mx
Set rs = CurrentDb.OpenRecordset("select myField from tblMyTable")
With rs
.MoveFirst
Do
mx = maxChar(!myField)
Debug.Print !myField, mx & "(" & ChrW(mx) & ")" '(Hit CTRL+G to view)
.MoveNext
Loop Until .EOF
.Close
End With
Set rs = Nothing 'always clean up your objects when finished with them!
Set excel = Nothing
End Sub
Function maxChar(st As String)
Dim b() As Byte 'declare Byte Array
ReDim b(1 To Len(st)) 'resize Byte Array
b = StrConv(st, vbFromUnicode) 'convert String to Bytes
maxChar = excel.WorksheetFunction.Max(b) 'find maximum Byte (with Excel function)
End Function
Because they probably thought that you would use DMAX and DMIN or the sql MAX and only working with the database in access?
Im also curious about why.. Its seems like a overkill to have to create a temp-table and add form values to the table and then run a DMAX or MAX-query on the table to get the result...
I've been known to create a small projMax() function just to deal with these. Not that VBA will probably ever be enhanced, but just in case they ever do add a proper Max (and Min) function, it won't conflict with my functions. BTW, the original poster suggests doing IIF... That works, but in my function, I usually throw a couple of Nz()'s to prevent a null from ruining the function.
Both functions have problems with Null. I think this will be better.
Public Function iMin(ParamArray p()) As Variant
Dim vVal As Variant, vMinVal As Variant
vMinVal = Null
For Each vVal In p
If Not IsNull(vVal) And (IsNull(vMinVal) Or (vVal < vMinVal)) Then _
vMinVal = vVal
Next
iMin = vMinVal
End Function
I liked DGM's use of the IIF statement and David's use of the For/Next loop, so I am combining them together.
Because VBA in access does not have a strict type checking, I will be using varients to preserve all numerics, integer and decimal, and re-type the return value.
Kudos to HansUP for catching my parameter verification :)
Comments added to make code more friendlier.
Option Compare Database
Option Base 0
Option Explicit
Function f_var_Min(ParamArray NumericItems()) As Variant
If UBound(NumericItems) = -1 Then Exit Function ' No parameters
Dim vVal As Variant, vNumeric As Variant
vVal = NumericItems(0)
For Each vNumeric In NumericItems
vVal = IIf(vNumeric < vVal, vNumeric, vVal) ' Keep smaller of 2 values
Next
f_var_Min = vVal ' Return final value
End Function
Function f_var_Max(ParamArray NumericItems()) As Variant
If UBound(NumericItems) = -1 Then Exit Function ' No parameters
Dim vVal As Variant, vNumeric As Variant
vVal = NumericItems(0)
For Each vNumeric In NumericItems
vVal = IIf(vNumeric < vVal, vVal, vNumeric) ' Keep larger of 2 values
Next
f_var_Max = vVal ' Return final value
End Function
The only difference between the 2 functions is the order of vVal and vNumeric in the IIF statement.The for each clause uses internal VBA logic to handle the looping and array bounds checking, while "Base 0" starts the array index at 0.
You can call Excel functions in Access VBA:
Global gObjExcel As Excel.Application
Public Sub initXL()
Set gObjExcel = New Excel.Application
End Sub
Public Sub killXL()
gObjExcel.Quit
Set gObjExcel = Nothing
End Sub
Public Function xlMax(a As Double, b As Double) As Double
xlCeiling = gObjExcel.Application.Max(a, b)
End Function
You can do Worksheetfunction.max() or worksheetfunction.min() within Access VBA. Hope this helps.