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 subIf 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 subThis 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 subTo 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