Excel Force Calculation - validation

I have a cell that is evaluated by
=IF(OR(J41="",J40=""),"",(1-($J$41/$J$40)))
computing the percent error between two cells that the user inputs. Additionally, I have an IFC on a seperate sheet that is validating this cell, among other cells, and setting it to a certain color with a warning if the percent error value is above/below a certain number. The problem is that the cell does not show the warning unless I click on it and hit enter (or F2 + Enter), which calculates the cell and populates the value at that time. Is there a way to Force the calculation to occur in that cell when the user inputs values into J41 and J40, thus populating the warning immediately? I have checked multiple threads on this, some say use the Application.Volatile statement, but I am not too sure if that will work..
Any suggestions?

Manually calculating a range is as simple as using Range.Calculate.
To run code when a value is changed in a certain range, use Worksheet_Change.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngTrigger As Range: Set rngTrigger = Range("D5")
Dim rngTarget As Range: Set rngTarget = Range("E5")
If Not Intersect(Target, rngTrigger) Is Nothing Then _
rngTarget.Calculate
End Sub

Related

Assigning a value to merged cells in Apple Numbers using AppleScript

As part of a longer AppleScript, I'm copying strings from a list variable into ranges of merged cells in a Numbers 10.0 table. E.g., if the list is called form_filler and the first range of merged cells is B6:Y7, I first tried:
set value of cell "B6" to item 1 of form_filler
I thought one addresses merged cells by the top-left cell. But this does something unexpected: it places the string only into cell "B6" and changes the range of merged cells to C6:Y7, excluding the cell I just pasted into. This behavior occurs consistently with different merged cells throughout the table. I then tried:
set value of range "B6:Y7" to item 1 of form_filler
but this returned an error; I can't assign a value to the range.
I'm new to AppleScript, but not programming generally (e.g., Python). What am I missing? Thanks.
It looks like you have to re-merge those cells. Here's code I just tested using my own tell block structure; you should be able to extrapolate from this (if you include your tell block structure I'll edit my code):
tell application "Numbers"
set d to sheet 1 of document 1
tell d
set value of cell "B6" of table 1 of it to "test"
merge range "B6:Y7" of table 1 of it
end tell
end tell
Not sure if this qualifies as a "work-around", but it seems to work, hopefully w/o introducing other issues.

Missing events in properties for VB6

I am a self taught amateur Visual Basic programmer. My programs are for myself, friends and some non-profit organizations. I have a situation that I thought would be reasonably simple but doesn't seems the case.
I have a text box array of 6 elements (1 -6) named "txtBilled" .When the value is entered into any but element 6 I want to add the values in 1-5 and put result in element 6. My problems start due to the fact that the properties for the text array does not provide for a lost focus option. Searching the inter provides statements that this is normal, others say the "Lost Focus" should always be there.
As a second approach I tried to use the validate element. Never used this before created a sub as follows that I found on the web.
Private sub txtBilled__Validate(Cancel as Boolean)
Found that the Validate event is also not included in the properties for the array
I am using VB6 version 8176 under Windows 10.
Any ideal as to what I am doing incorrectly would be appreciated.
Can you not create your own LostFocus sub using the Index of the textbox array?
Private Sub txtBilled_LostFocus(Index As Integer)
Dim i As Integer
dim sngTotal As Single
' Calculate sum only if not in last textbox
If Index <> uBound(txtBilled) Then
For i = LBound(txtBilled) to UBound(txtBilled) - 1
sngTotal = sngTotal + txtBilled(i)
Next i
txtBilled(uBound(txtBilled)) = sngTotal
End If
End Sub

Excel VBA - Any performance benefits between const string range or define names for ranges?

G'Day,
I have a question more towards helping me understand on more about how Excel VBA can effectively manage defined ranges that have been declared in one place in order to execute data well. Just wanting to work out which two options (I know so far) is better or not as preferred best practice before working more on this project.
The problem I'm solving is to make a small table containing a number of failures across a set of fictional suppliers, thus the table looks like this (sorry it is in raw form)
"Company Name" "No. of Failures"
"Be Cool Machine" 7
"Coolant Quarters" 5
"Little Water Coolants 3
"Air Movers Systems" 7
"Generals Coolant" 5
"Admire Coolants" 4
My first option (Const String) is this module/formula as follows.
Option Explicit
Public Const CountofFailures As String = "J7:J12"
Sub btnRandom()
' Declaration of variables
Dim c As Range
' Provide a random number for failures across Suppliers
For Each c In ActiveSheet.Range(CountofFailures)
c.Value = Random1to10
Next c
End Sub
Function Random1to10() As Integer
'Ensure we have a different value each time we run this macro
Randomize
' Provide a random number from 1 to 10 (Maximum number of Failures)
Random1to10 = Int(Rnd() * 10 + 1)
End Function
Second option (Defined Name) is this module/formula as follows.
Option Explicit
Sub btnRandom()
' Declaration of variables
Dim c As Range
Dim iLoop As Long
' Provide a random number for Suppliers with Defined Range
For Each c In ActiveWorkbook.Names("CountofFailures").RefersToRange
c.Value = Random1to10
Next c
End Sub
Function Random1to10() As Integer
'Ensure we have a different value each time we run this macro
Randomize
' Provide a random number from 1 to 10 (Maximum number of Failures)
Random1to10 = Int(Rnd() * 10 + 1)
End Function
Any suggestions - I would do a macro timer test later if this helps?
Would there be a third option if I fetch a range listed in a cell as value? I haven't seen a code that does this in practice?
I don't know the performance difference-I suspect const is faster. My general advice is 'don't worry about performance until you have a performance problem'. Otherwise you end up guessing what to spend your optimize time on and it may not be right.
As for named ranges, the benefit is that they move when you insert rows and columns. If you insert a new column at column I your first example needs to be edited and your second example will conitinue to work.
Both of your codes loop through ranges which will be the bottleneck. I suggest you
Use a range name to automatically "locate" your data - ie if you insert/delete rows and columns your reference remains intact. My experience though is that many range names in a file can end up obfuscating what the workbook is doing
Do a single write to this range
code
Sub QuickFill()
Randomize
Range("CountofFailures").Formula = "=Randbetween(1,10)"
Range("CountofFailures").Value = Range("CountofFailures").Value
End Sub
I have found that Named Ranges are slower (presumably because Excel has to do an internal lookup on the Name to find what it refers to), but you are very unlikely to be able to find a significant dofference except in very extreme cases (tens of thousands of names being referenced tens of thousands or hundreds of thousands times).
And as Dick says: the benefits far outweigh the insignificant speed loss.

vb6 - how to find largest element index in control array?

I am dynamically loading and unloading an array of command buttons on a form.
I can do this:
Dim UnloadIndex As Integer
For UnloadIndex = 1 To 20
Unload frmMain.cmdAction(UnloadIndex)
Next
But I don't always have 20 elements. Is there a way to loop through each one until it reaches the end?
I know I can use a global variable and track the value but I'm trying to avoid this.
Any suggestions please...
Use UBound() which returns the highest available subscript for the indicated dimension of
an array.
Dim UnloadIndex As Integer
For UnloadIndex = LBound(frmMain.cmdAction) To UBound(frmMain.cmdAction)
Unload frmMain.cmdAction(UnloadIndex)
Next
If they're not sequential, you could also do:
Dim Control as CommandButton
For Each Control in frmMain.cmdAction
If Control.Index > 0 Then
Unload Control
End If
Next
Dim UnloadIndex As Integer
For UnloadIndex = LBound(frmMain.cmdAction.LBound) To UBound(frmMain.cmdAction.UBound)
Unload frmMain.cmdAction(UnloadIndex)
Next
I found that the accepted answer way gives a compile error
expected array
Using dot notation instead worked for me.

VB6 control iteration: one control gives me an error; which one?

I am trying to loop through all controls in a form:
For Each ctrl in Me.Controls
in order enable/disable the control based on some conditions.
But there is a control on the form that gives an error when I try to access it. What kind of control does that, and how do I find it?
When you get your error and click Debug, is the error on the line setting a control's Enabled property?
If so, add a Debug.Print statement writing out the control's name. Do so on the line before setting the Enabled property.
Here's what I mean:
Dim ctrl As Control
For Each ctrl In Me.Controls
Debug.Print ctrl.Name
ctrl.Enabled = True
Next
The Debug.Print statement will write out to the Immediate Window the name of the control that was last processed in the loop, presumably the one that caused your error.
EDIT
This might work. Put this control in a Panel control and set the Panel's Enabled property to False. If I recall correctly, in VB6 setting a container control's Enabled property to False will also set the container's child controls Enabled to False. If your control's Enabled property really is read-only, I'm curious what would happen.
Try this:
Dim ctr As Control
Dim CtrStatus Boolean
CtrStatus = False
For Each ctr In Me.Controls
If (SSTab.hwnd = GetParent(ctr.hwnd)) Then
Call CallByName(ctr, "Enabled", VbLet, CtrStatus)
else
ctr.Enabled = CtrStatus
End If
Next
Another approach is as follows, that also works at runtime (as opposed to just in the IDE):
private sub SetEnabled()
on error goto errHandle
Dim ctrl As Control
For Each ctrl In Me.Controls
ctrl.Enabled = True
Next
exitPoint:
exit sub
errHandle:
MsgBox "Error " & err.Description & " with Control " & ctrl.Name
resume exitPoint
end sub
Suppress the error reports before the loop and then set it back to standard error handling:
On Error Resume Next
For Each ctrl In Me.Controls
ctrl.Enabled = lIsEnabled
Next
On Error GoTo 0
OR name your controls with a standard prefix/suffix that you can check by name and skip in the loop.
For Each ctrl In Me.Controls
If Left(ctrl.Name, 3) = "sst" Then
ctrl.Enabled = lIsEnabled
End If
Next
Tosa: from your comment on AngryHacker's answer, I think you are checking the container incorrectly.
Your code is like this
' BAD CODE '
If ctrl.Container = fraMovies Then
For me that gives error 450 Wrong number of arguments or invalid property assignment. Do you get the same error?
The code should use Is rather than =, like this
' GOOD CODE '
If ctrl.Container Is fraMovies Then
Explanation. You want to check whether two variables "point" to the same control. Controls are objects: you must use Is not = to check whether two object variables "point" to the same object. This is a classic pitfall in VB6.
One last word. Next time, could you try to post 10 lines or less of actual code, reproducing the error, and give the exact error number and message and the exact line on which it occurs? It really does make it much easier for us to solve your problem - I know it's work for you to shorten the code, but you'll get better answers that way.
EDIT Welcome back! :) You said some controls don't have a Container property. You could try wrapping the test in On Error Resume Next, something like this.
' GOOD CODE '
Dim bMatch As Boolean
Dim ctrl As Control
For Each ctrl In Me.Controls
bMatch = False
On Error Resume Next
bMatch = (ctrl.Container Is fraMovies)
On Error Goto 0
If bMatch Then
ctrl.Enabled = True
End If
Next
To avoid such problems follow the given rules while naming contols
When you name an element in your Visual Basic application, the first character of that name must be an alphabetic character or an underscore.
**Begin each separate word in a name with a capital letter, as in FindLastRecord and RedrawMyForm.
Begin function and method names with a verb, as in InitNameArray or CloseDialog.
Begin class, structure, module, and property names with a noun, as in EmployeeName or CarAccessory.
Begin interface names with the prefix "I", followed by a noun or a noun phrase, like IComponent, or with an adjective describing the interface's behavior, like IPersistable. Do not use the underscore, and use abbreviations sparingly, because abbreviations can cause confusion.
Begin event handler names with a noun describing the type of event followed by the "EventHandler" suffix, as in "MouseEventHandler".
In names of event argument classes, include the "EventArgs" suffix.
If an event has a concept of "before" or "after," use a suffix in present or past tense, as in "ControlAdd" or "ControlAdded".
For long or frequently used terms, use abbreviations to keep name lengths reasonable, for example, "HTML", instead of "Hypertext Markup Language". In general, variable names greater than 32 characters are difficult to read on a monitor set to a low resolution. Also, make sure your abbreviations are consistent throughout the entire application. Randomly switching in a project between "HTML" and "Hypertext Markup Language" can lead to confusion.
Avoid using names in an inner scope that are the same as names in an outer scope. Errors can result if the wrong variable is accessed. If a conflict occurs between a variable and the keyword of the same name, you must identify the keyword by preceding it with the appropriate type library. For example, if you have a variable called Date, you can use the intrinsic Date function only by calling DateTime.Date.

Resources