|
|
Tech Note 34: |
The NS Basic/CE environment has been designed so there's a lot of power in some very simple instructions. Once you've got the hang of it, there are some advanced things you can do with NS Basic/CE that would be tough to do in most other languages.
Here are some of the cool things we've discovered. Let us know if you come up with other things!
EXECUTE to use parameters as referencesPrivate Sub setupButton(name, text)
execute name & ".borderstyle=1"
execute name & ".drawText" & chr(34) & text & chr(34)
end sub
This subroutine sets the name of a button to text and draws a line around the box. It is called with the name of the button and the text you want to show. CHR(34) is the quote sign (").
EXECUTE takes a string and feeds it to the interpreter, just as if it were a statement in your program.
If you do
SetupButton("myButton","Tap here")
This is what gets executed within the subroutine:
myButton.drawText "Tap Here"
The two execute statements could actually be combined into a single statement, by concatenating a return (vbCrLF) between the two strings.
NS Basic/CE lets you create objects at any time, not just when you build the project. Building on our previous example, here's a subroutine that creates the button object as well as sets it up:
Private Sub setupButton(name, text, x,y,w,h)
addObject "PictureBox",name, x,y,w,h
execute name & ".borderstyle=1"
execute name & ".drawText" & chr(34) & text & chr(34)
end sub
Since we can create objects only as needed, memory requirements can be kept lower. It may also may it easier to design the UI.
This one is really wild, and builds on the previous two Cool Things. The following code creates n buttons on the screen, all of them clickable:
Option Explicit
dim count,i
count=inputbox("How many do you want?")
for i=1 to count
makeButton "B" & i,cstr(i),i
next
Private Sub makeButton(name, prompt,b)
dim code
addObject "PictureBox",name & "Btn",(b mod 25)*25, int(i/25)*25,20,20
execute name & "Btn.borderstyle=1"
execute name & "Btn.drawText" & chr(34) & prompt & chr(34)
code="sub " & name & "Btn_click()" & vbcrlf & "print " & chr(34) & "click at " & name & chr(34) & vbcrlf & "end sub"
execute code
end sub
In this case, we're doing everything we did in the first two. However, we're taking advantage of one additional thing: The runtime environment is composed of both the variables and the code. In Cool Thing 1, we modified the values of variables in the runtime from our program. In this one, we're actually modifying the code, by adding a subroutine for each button that we create. When the button is then tapped, the event is sent to the new subroutine we created.
It is not obvious that you can create control arrays in NS Basic. This program creates 6 commandbuttons and executes a set command to put them into an array called cmdButtons. (Contributed by Terry Myhrer)
option explicit
dim cmdButtons(5), index
updatescreen
for index = 0 to 5
MakeButton(index)
cmdButtons(index).caption = "Button " & cstr(index)
next
sub ButtonClick(byval number)
if cmdButtons(number).caption = "Clicked" then
cmdButtons(number).caption = "Button " & cstr(number)
else
cmdButtons(number).caption = "Clicked"
end if
end sub
sub MakeButton(byval number)
dim name, code
name = "btnButton" & cstr(number)
addobject "CommandButton", name, 10, number * 30, 100, 25
execute " Set cmdButtons(" & cstr(number) & ") = " & name
code = "Sub " & name & "_Click()" & vbcrlf & " ButtonClick " & cstr(number) & vbcrlf & "end sub"
execute code
end sub
Const BS_MULTILINE = &H00002000in Form_Load, do the following (This can be done in another sub, but then you have to move the object at least 1 pixel to get it to redraw and use the new setting)
CommandButton1.WindowLong(0) = BS_MULTILINEWindowLong is a powerful feature to manipulate the appearance of objects. You'll need to look at Microsoft's documentation to get the full information.
Dim nextCtrl
sub activeCtrl_GotFocus
nextCtrl = "otherControl"
end sub
sub output_keyup (keycode, shift)
if keycode = 9 then '9 is tab key
keycode = 0 'should prevent system from firing anything
execute nextctrl & ".SetFocus"
end if
end sub
If your controls are out of order, the
execute line fires but then the keycode 9 goes to the system and refires
the setFocus to the systems next in the creation order control list and
then end result is that focus doesn't go to the nextCtrl. Keep reading - the next item is also important...
Declare "Function SendMessageStr Lib ""Coredll"" Alias ""SendMessageW""
(ByVal HWND As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal
lParam As String) As Long"
Dim bReset
Const CB_FINDSTRING = &H14C
Const CB_FINDSTRINGEXACT = &H158
Const CB_SETEDITSEL = &H0142
sub combobox_change
if bReset then exit sub 'prevents endless loops
bReset = true
getIdx "combobox"
bReset = false
end sub
sub getIdx(ctrl)
Dim idx
'finds the idx of the exact match (case insensitive) inside the combobox.List
Execute "idx = sendMessageStr(" & ctrl & ".hwnd, CB_FINDSTRINGEXACT, 0 , " & ctrl & ".Text)"
If idx > -1 Then 'only assign when match found otherwise constantly resetting to -1
Execute ctrl & ".Listindex = " & idx 'setting the ListIndex causes the box to select all text
'following turns off text selection inside control
Execute "SendMessageStr " & ctrl & ".hwnd, CB_SETEDITSEL, 0, -1"
End If
end sub
This getIdx routine is called when the user types into a combobox text
window which fires the onChange event. The problem is, setting the
ListIndex inside of the onChange event only lasts inside the event, once
the event dies, the ListIndex becomes what is was before the getIdx routine.
SOLUTION
I fought this and the tabbing with all sorts of possible tags, techniques and procedures until I suddenly realized I had to do both of these procedure after the subroutine in which they are called. My solution is to delay execution of my routine until after the system has finished with its routine by using the NSBasic timer feature.
To the 1st Example add a timer event, this technique does not require re ordering creation of controls.
Dim nextCtrl
sub activeCtrl_GotFocus
nextCtrl = "otherControl"
end sub
sub activeCtrl_Timer
activeCtrl.Timer = 0 'turn off timer event so it fires only once
if nextCtrl = "" then exit sub 'prevents invalid code
execute nextCtrl & ".SetFocus" 'because using generic nextCtrl this routine can be used by any other tabbing event
end sub
sub otherCtrl_GotFocus
nextCtrl = "activeCtrl" 'same keyup event will allow tabbing back to activeCtrl regardless of creation order
end sub
sub output_keyup (keycode, shift)
if keycode = 9 then '9 is tab key
keycode = 0 'should prevent system from firing anything
execute activeCtrl.Timer = 25 'now your setFocus happens just after the system setFocus
end if
end sub
To the 2nd Example again add a timer event and add another global variable.
Dim bReset, thisCtrl 'by adding this variable you can use the routine in
'the one timer event from other comboboxes without duplicating timer events
Const CB_FINDSTRING = &H14C
Const CB_FINDSTRINGEXACT = &H158
Const CB_SETEDITSEL = &H0142
sub combobox_change
if bReset then exit sub 'prevents endless loops
thisCtrl = "combobox"
combobox.Timer = 25 'timer event fires in 25 ms, plenty of time for system to complete ending the change event
end sub
sub combobox_Timer
combobox.Timer = 0 'turn off timer so it only fires once
bReset = true 'the code below will cause a onChange event this flag allows our code to exit gracefully
getIdx thisCtrl 'generic call using thisCtrl allows code to be reused by other comboboxes
bReset = false 'reenables the rest of the onChange event based on user input
end sub
sub getIdx(ctrl)
Dim idx
Execute "idx = sendMessageStr(" & ctrl & ".hwnd, CB_FINDSTRINGEXACT, 0 , " & ctrl & ".Text)"
If idx > -1 Then
Execute ctrl & ".Listindex = " & idx
Execute "SendMessageStr " & ctrl & ".hwnd, CB_SETEDITSEL, 0, -1"
End If
end sub