Tipswip   >   Windows   >   Windows (All Contents)
The window is the where the user and your application intersect, hence an important part for your application, both visually and functionally.
There are books and books of information on good user interface practices. Be sure to check out the Books > GUI Design section of StudioTips.
This section includes various tidbits of code and ideas which apply to window classes.The following email snipit from David Barnett is the clearest explanation of the behaviour of $cobj.$contents that I have read.
__________
Something to remember about $contents vs $dataname. While you're typing in a field, key events are happening (if turned on), and $cobj.$contents is getting filled in. BUT the actual variable designated by $dataname doesn't 'see' the newly entered stuff UNTIL a 'real' (non-key) event happens, like an #OK, #AFTER, #CLICK, etc. That's the point when the $contents gets 'written into' the variable.
If you are managing keystrokes with key events, you need to do it manually. And if I remember right, when the key event happens, you get the key which was struck, but it's not even in $contents yet. You would have to concatenate the key and $contents to know the entire entry (to that point). I remember doing something like that for a password change window. I would 'watch' the 're-enter new password' field key by key, and enable the 'OK' button when the user matched the new password they entered in the previous field.
So think of $contents as a 'keyboard buffer' which gets 'written into' the $dataname variable when the focus leaves the entry field.
Oh, and if you were entering a field within a complex grid, you needed the 'list.dataname' notation to get the $contents into the cell in the current line within the Omnis list, as opposed to putting it into the CRB version of the variable (which isn't 'in' the list).
David BarnettWhen a user clicks on an entry field for the first time, I like to select all the text in the field, giving the same behaviour as though they tabbed into the field. To do this add the following to either the field object's $event method, or better yet the window's $control method. You could replace $cobj with $cfield, but it will only work in the field's $event method. $cobj works in the field event and the control event.
On evBeforeEver wonder what "hwnd" stands for? I wrote to tech support to find out. "hwnd" stands for "Handle To A Window". It's a Microsoft term. All GUI objects in Windows are drawn in HWND's
Positioning a window relative to an object on another window can be very tricky.
Window frames, title bars, menus, and toolbars all affect the exact pixel location of window objects relative to the overall screen height and width.
Add to that subwindows, complex grids, and group boxes and you've got a tangled web of code to try and figure out the exact location of an object on a window relative to the application window.
The $top position of an object in a window is only relative to the "container" which the object is located in. So when $top of an object is set to zero, the object will be position at the topmost position on the window area available to window objects.
Imagine a window which has a title, menu, and toolbar.
$cwind.$top is the top of the window.
$cwind.$toplevelhwnd.$ref().$top is the bottom of the title bar.
$cwind.$framehwnd.$ref().$top is the bottom of the menu bar? Whether or not it is visible. :-(
$cwind.$hwnd.$ref().$top - I've had no luck with this one so far.
Note: The () in the above notation strings are needed to force Omnis to first evaluate the string to that point.
Note: If you use $cinst for $toplevelhwnd and $framehwnd, the code will break if the window is a subwindow.
$modes.$top is the top of the screen available for positioning the window. $modes.$height is the total height of the screen available for displaying the window.
At the time of writing this demo I was having a hard time getting $hwnd to work.
What would be REALLY nice is for engineering to give us a way of returning the top and left position of a reference object on a window, relative to the screen.
Click the Run Demo button to test the various window and hwnd positions.When you assign the $iconid property to a pushbutton Studio defaults to a k16x16 size icon. To control the size of the icon when assigning the $iconid you need to do the following:
Do PushButtonRef.$iconid.$assign(2105+k48x48)
Adding the icon size constant to the icon ID controls the size of the icon.$redraw(bSetContents,bRefresh)
Method to redraw the contents and/or refresh a field or window.
setcontentsı defaults to true, refreshı defaults to false.
Do $cfield.$redraw() ;; redraws the current field
Do $cwind.$redraw() ;; redraws the current window
Do $root.$redraw() ;; redraws all window instances
The $redraw parameters have been a mystery to me for a long time. Hopefully this tip and demo will help to demystify it for others as well.
It took a few hours to build the demo for this tip to test and analyze the $redraw method and draw some conclusions as to what bSetContents and bRefresh actually do.
Every entry field has a $dataname property. You enter the name of the variable which you want the entry field to display. $dataname could be a task, class, or instance variable. The variable could be a column in a list or row, or a simple variable.
Every entry field has a $contents property. The $contents property is normally equal to the value of the variable being represented by the entry field... but not necessarily.
When you enter the field (evBefore), Omnis refreshes $contents with the variable value.
If you modify the field, $contents will be DIFFERENT than the variable value while your are still in the field. When you leave the field (evAfter) Omnis copies the $contents to the variable. Omnis does this JUST before sending the $event(evAfter) message to the field. (That's what makes it difficult to test and figure out WHEN $contents is copied to the variable.)
bSetContents=kTrue will set $contents to match $dataname.
bRefresh=kTrue will refresh the field contents.
If you are changing the variable value, you want to bSetContents to be true. (default). If you are changing the $contents you want bSetContents to be false.
Changing $contents ONLY seems to work if the focus in on the field. If the focus is on another field $contents gets washed away by the variable value when you enter the field.
I had hoped to find a way to notationally "push" the $contents TO the variable but didn't succeed. You can test all of the above with the demo included with this tip.
I didn't figure out a lot of use for setting bRefresh to true vs. false. If you know situations where setting bRefresh to true vs. false is important, please let me know and I can add your suggestions to this tip.If you want to have a calculated display entry field in a window or a complex grid there are 2 entry field properties which you need to set:
$calcualated = kTrue
$enabled = kFalse
You can then enter your calculation in the $text property of the entry field, or you can assign the $text property of the field from a method.
If you don't set both properties, Omnis Studio will not display your $text calculation or value.
Click Run Demo to see a calculated entry field being used in window field and a complex grid.The following snipit from the list server talks about centering windows on the screen.
___________
I am trying to open a window in the centre of the screen. I am using:
Do $windows.WindowName.$openonce('*',kWindowCenter)
The windows I need to open are based on a superclass and are all vastly different sizes - the sizes being set in the individual windows '$construct' method.
They ARE being centred, but it is based on the size of the superclass window in 'Design' mode (where it is much bigger than most of the windows).
What I would like to do is to centre the window in the construct AFTER it has been re-sized - I
can't see how to achieve this - I am open to any wisdom if there is a better way, like adding size
parameters to 'openonce'
Thanks as always, Greg Olson-Hyde
___________
You could try putting this code in the construct of the window (after the resizing part):
Calculate $cinst.$top as ($modes.$height()-$cinst.$height())/2
Calculate $cinst.$left as ($modes.$width()-$cinst.$width())/2
Regards, Marten Verhoeven
___________
My users seem to find the centering more pleasing when you use:
Calculate $cinst.$top as ($modes.$height()-$cinst.$height())/8
It puts it above center where alert dialogs ususally show up. try it!
John Kenall$cobj - is the current object that has focus or where the event is occurring. Click a button it is $cobj $cfield - is the field which the method belongs to.
If you want a method to run in the $construct of a field, $cfield is the reference to that field in the field's $construct (I used to try $cobj, but during the $construct the $cfield is not the $cobj.You can change the behaviour of the 'Enter' key to equal the behaviour of the 'Tab' key. If your application has a lot of numeric data entry your users will greatly appreciate this simple user interface change, and you boss will appreciate the increased productivity.
In my application we have timesheet entry, Purchase Order entry, Inventory Disbursements, Accounts Payable Invoice entry, Accounts Receivable Invoice entry. All of the tasks make heavy use of the numeric keypad. You would be amazed at the time saved when the user can use the 'Enter Key' to move from field to field, rather than reach for the 'Tab key'. In our application time sheet entry increase 30% by this simple change.
Many of the old 'green screen' systems used the Enter Key to move from field to field, so this in not a foreign user interface. (Actually coming from a green screen the inability to use the Enter Key to move from field to field was foreign to me.)
The code for doing this is extremely simple (See sample code below). You can either put it in the $control method of your superclass window, or in the $control method of your Startup_Task. Make sure that $keyevents is set to kTrue in the properties of all your libraries.Do you use group boxes to group your radio buttons? They work great especially as containers for radio buttons. Move the group box around in design mode and you're moving its contents just as you laid them out. Disable it and you've disabled its contents.
Thanks to Craig Lewis for this tip.
SCROLL BOXES
I prefer scroll boxes over group boxes. The top edge of the group box takes up too much space if I'm not using the group box title. If you set the vertical and horizontal scrollbars to kFalse in a scroll box you've got a group box with narrow borders.You can trap keyboard events with "On evKey" in the $event field method. (or the window $control method if the event is passed to that level.)
The "evKey" event is sent to the $event method with the parameters "pKey" and "pSystemKey"
"Tab" and "ShiftTab" have their own event codes, "evTab" and "evShiftTab". You can (and should) trap "On evTab" and "On evShiftTab" rather than their system key equivalents for easier to read code.
Note: In your application make sure $keyevents is set to kTrue in your library properties or the object properties.If you have a multi-line entry field that has 2378 characters and random carriage returns, how do you figure out the height of the text in the field?
Way back in 2000 Tech Support sent me a library which included some code for figuring out the height of the text in a multi-line entry field. The code worked but the notation is so unconventional that I assume this must be an undocumented feature.
I use this trick for setting the height tip description field in the StudioTips Browser window.
The code is listed below. Click the Run Demo button to test the code.Craig uses 2 lower case letters to indicate what kind of critter the object is, followed by a meaningful name for the object. Thus when looking in the methods editor you can easily tell what object you've got.
pbOK - pushbutton for OK
rbType01 - second radio button in a group
cgEntryList - complex grid entry list
efName - entry field for a name
dfRSN - display field for the record sequence number
cbDoIt - check box that I couldn't resist putting here
The question: How can I remove the objects from a window instance?
-----
Email reply from Doug Kuyvenhoven:
I don't have a one liner for you. I expect Studio would have problems removing its own objects in a $sendall.
The best I could do was a $makelist followed with a For lobject-oriented programming
Do $cinst.$objs.$makelist($ref) Returns List
For List.$line from 1 to List.$linecount step 1
Do $cinst.$objs.$remove(List.C1)
End For
Tim Stewart (the $sendall pro) could likely crack the code for using a $sendall to replace the for lobject-oriented programming. (Another challenge for you Tim! :-)
-----
Email reply from Tim Stewart:
Piece of cake! (OK, I'm getting smug now) :-) The line you need is:
Do $cinst.$objs.$sendall($cinst.$objs.$remove($cinst.$objs.1.$ref))
The key to this is again that $ref always points to the last object referred to. If you were to say:
$cinst.$objs.$remove($ref)
It would point to the collection $cinst.$objs - in other words trying to remove the collection from itself.
But, the first member of the collection can be referenced as $collection.1.$ref. What the above
sendall does is, for each object in the collection, removes the first object in the collection (in other words, itself).
REMOVING A SPECIFY TYPE OF OBJECT
The $sendall solution only works for removing all of the objects. If you wanted to just remove kOval background objects, you couldn't use the $sendall with $remove.
One workaround, suggested by Weitse Jacobs, is to set $visible to kFalse for the objects.
Do $cinst.$bobjs.$sendall($ref.$visible.$assign(kFalse),$ref.$objtype=kOval)
Another workaround, suggested by Günther Feichtinger, is to use $makelist, and then $sendall to the list.
Do $cinst.$objs.$makelist($ref,$ref.$objtype) Returns List
Do List.$sendall($cinst.$bobjs.$remove(List.C1),$ref.C2=kOval)
ADDITIONAL NOTE (FROM JEAN_MARC AZERAD)
There does seem to be a problem with objects added on the fly.
The conclusion is that a direct $sendall with condition doesn't work with $remove() for windows objects. The $visible workaround did not work for me as the objects are created on the fly and this the $visible property doesn't seem to be very stable in that case as soon as the window is refreshed, (just because another window is open on the top of it for example) objects come back to visible (another bug ?) So I ended up with maintaining a list of my objects and a lobject-oriented programming ...
Thanks again for your help, I could have spent hours before admitting the $sendall wouldn't work !
Jean_Marc AzeradIf you make a resizeable window it's nice to have the objects on the window correctly reposition themselves as the window is beign resized.
A technique I use extensively is to set the $edgefloat property of objects to: kEFposnTopToolBar, kEFposnBottomToolbar,kEFposnLeftToolBar, kEFposnRightToolBar, and kEFposnClient.
Many times it works best to add a page pane or scrollbox object, set its $edgefloat property, and then add object inside the page page or scrollbox object.
For example a set up pushbuttons along the bottom of the window would be placed in a paged pane object which we call "BottomPane". The paged pane object $edgefloat would be set to kEFposnBottomToolBar. Now no matter what size the user resizes the window to, the "BottomPane" with its group of buttons will hug the bottom of the window. If you want to have the buttons hug the right bottom edge of the window, put another paged pane object inside the "BottomPane" and name it "BottomPaneRight" setting its $edgefloat to kEFposnRightToolbar. Size its width correctly and move the buttons into the "BottomPaneRight" paged pane. The group of buttons will now always hug the bottom right edge of the window.
TIP: To make life easier in design mode, for any kEFposnClient objects, set the object to kEFnone in the F6 Property Manager, and add a $construct method to the object with a single line of code; "Do $cfield.$edgefloat.$assign(kEFposnClient)"
TIP: Paged Pane objects seem to resist being resized below a certain height or width. When you attempt to resize the window instance smaller, the paged pane objects can cause the window instance to be resized bigger and not allow the user to resize it smaller! The solution I found was to use scroll box objects instead of paged pane objects (if you are only using the first pane anyway). You can make as scroll box object look like a page pane by setting the $vertscroll and $horzscroll to kFalse, and the $fieldstyle to CtrlPagedPane. The bottom line: Use scrollbar objects as containers rather than paged pane objects where possible.
Press the demo button to see the way that sroll boxes and paged pane objects are used in the code documentor window.The following question and solution copied from the list server show a situation where a user wishes to 'revert' window objects in a window instance to their original attributes.
__________
QUESTION:
I'm tring to reverse a sendall command. The initial command is something like:
$cinst.$objs.$sendall($ref.$forecolor.$assign(kRed),($ref.$objtype=kEntry)|($ ref.$objtype=kMaskedEntry))
To change the color of all entry fields in a window...
I'd like to reverse it with a command like :
$cinst.$objs.$sendall($ref.$forecolor.$assign(KDEFAULT),($ref.$objtype=kEntry )|($ref.$objtype=kMaskedEntry))
To get back to the default properties. The fields are colored in different ways and I want to make sure each comes back to its own original color.
Of course the kDefault constant does not exist. Any suggestion ?
TIA, Jean_Marc
__________
SOLUTION:
If you are making these changes only in an instance of the class and the library code is left untouched then you could restore the values in the instance with those from the library. For example, where rObject is a variable of the data type Item reference.
Set reference rObject to as $cinst.$objs.$first()
While rObject
If rObject.$objtype=kEntry|rObject.$objtype=kMaskedEntry
Do rObject.$forecolor.$assign($cclass.$objs.$findname(rObject.$name).$forecolor()>)
End If
Set reference rObject to as $cinst.$objs.$next(rObject)
End While
HTH, Mark Phillips
__________
ADDITIONAL COMMENTS:
1. You can set a reference using "Do $cinst.$objs.$first Returns rObject" (minor matter of style)
2. It is possible to have 2 fields with the same $name, so using $ident might be safer.
The following is an alternate method for accomplishing the same solution.
Do $cinst.$objs.$first Returns rObject
While rObject
If rObject.$objtype=kEntry|rObject.$objtype=kMaskedEntry
Do rObject.$forecolor.$assign($cclass.$objs.[rObject().$ident].$forecolor())
End If
Do $cinst.$objs.$next(rObject) Returns rObject
End While
$sendall is really handy when you want to "Do" something to all the objects in your window.
The following are a examples of some scenarios and the syntax for doing them.
CHANGE A PROPERTY OF ALL OBJECTS
Change the foreground colour of all objects to kColor3Dface
Do $cinst.$objs.$sendall($ref.$forecolor.$assign(kColor3DFace))
CHANGE A PROPERTY OF SELECTED OBJECTS USING THE SEARCH CRITERIA OPTION
Disable all the objects which have a foreground color of kColor3Dface
Do $cinst.$objs.$sendall($ref.$enabled.$assign(kFalse),$ref.$forecolor=kColor3DFace)
Note the extra search criteria after the comma in the $sendall(). You can make this as complex as you like.
CHANGE A PROPERTY OF ALL OBJECTS RELATIVE TO THE OBJECT'S CURRENT PROPERTY
Move all objects right by 50 pixels
Do $cinst.$objs.$sendall($ref.left.$assign($ref().$left+50))
Note the $ref() inside the $assign(). This tells Studio to "evaluate $ref now", then continue evaluating the string.
CHANGE A PROPERTY OF SELECT OBJECTS RELATIVE TO THE OBJECT'S CURRENT PROPERTY
Move all kEntry fields right by 50 pixels
Do $cinst.$objs.$sendall($ref.left.$assign($ref().$left+50),$ref.$objtype=kEntry)
CHANGE PROPERTY OF OBJECTS INSIDE A SINGLE ROW OF A COMPLEX GRID (GRID EXCEPTIONS)
Subject: O$ Complex Grid $sendall to fields in a container object
I've got a group of 4 fields in a complex grid which I want to disable and change the foreground color on the current row only. No problem doing this with the 4 individual fields. This works for a single field:
Do GRIDREF.$objs.FIELDNAME.[LIST.$line].$forecolor.$assign(kGray)
But then I tried to get smart and put them in an invisible scroll box container field and use $sendall to do the job. I couldn't seem to get the syntax right. Maybe someone can help me out.
Here's what I tried: (plus a few other variations)
Do GRIDREF.$objs.CONTAINER.[LIST.$line].$sendall($ref.$forecolor.$assign(kGray))
Do GRIDREF.$objs.$sendall($ref.[LIST.$line].$forecolor.$assign(kGray))
The solution thanks to Marten Verhoeven
I think it should be this:
Do GRIDREF.$objs.$sendall($ref.[LIST.$line].$forecolor.$assign(kGray))
regards,
Marten VerhoevenWhat are they are and why would anyone use them? They're critters that look like background objects but they can receive events and contain methods.
Example: Use a shape field with its $shape property (Appearance tab of the Property Manager) set to kText as a column heading label in a complex grid. Then put your sort list code into the $event method of the shape field.
Thanks to Craig Lewis for this tip. Prior to receiving this tip from Craig I had never used a shape field. The only problem I have is because they are/look like text fields I keep double-clicking them when I want to change the text ... oops ... you can only change the text in the Property Manager.One of the first complaints I had when starting development with Studio was the length of time it took to open windows. In one of my first windows I loaded it with a 5 tab-tab pane and about 100 fields. When the window opened I was shocked at how slow it opened. Complaints to Tech Support didn't change things. The simple fact is that all the great benefits of OO cost something when it comes to opening up windows. You can have multiple instances of any window, you can have superclass inheritance, you can have $constructs and $events at every level, so just imagine all the work that Studio has to do when you instantiate a new window instance.
The thing I do to speed up window instantiation is to use empty subwindows in page panes and tab panes.
Lets say you have a 5 layer tab pane. When the user opens the window they only need to see the first pane. That might be all the information they need before closing the window. They might click on tab 3 and then close the window. You can speed up window performance by putting an empty subwindow field set to kEFposClient on each layer of the tab pane and leaving the $classname empty. In the Tab Pane $event method you trap the On evTabSelected and based on pTabNumber you $assign the $classname before the tab is displayed. In the $construct of the window or the tab pane you $assign the $classname for tab 1.
In my application I have a superclass window which contains a 5 layer page pane. Layer 2 of the page pane contains a 5 layer tab pane. All layers have a single subwindow field set to kEFposClient. The superclass window is used as the 'shell' for virtually every window in my application. The subwindows are assigned on the fly when the superclass window is instantiated.
The other advantage of using this subwindow technique besides speed is that each subwindow is 'flat', making it a lot simpler to layout, modify, etc. It takes a while to get used to using subwindows, but once you get past that I think you'll enjoy them.Many of the popular software programs (Outlook Express, Quicken,...) try to finish data entry for the user by finding matching entries and adding the text from the first match to the field while the user is typing. I call this "type ahead".
The kCombo field allows you to build a droplist which the user can select, but it doesn't handle "type ahead" in the same way as I experience it in Quicken or Outlook.
Phil Carling gave me the idea to combine a kEntry field and a kCombo field to obtain the UI behavior I was looking for. It took the better part of a day for me to get the UI working the way I wanted.
Click the Run Demo button to try out the type ahead demonstration.
The code in the demo window is in the field $event methods and is well commented.Omnis used the name "Complex Grids" for a reason. They are powerful, but they are complex.
Click "Run Demo" to review some complex grid notation in a demo window.The row number is not required when setting $ctarget. The target will be on the current line in the datalist represented by the complex grid.
Do $ctarget.$assign($cinst.$objs.ComplexGrid.$objs.FieldName)
Do $ctarget.$firstsel.$assign(0)
Do $ctarget.$lastsel.$assign(1000)
COMPLEX GRID $clearexceptions
$clearexceptions had always confused me. I thought it would revert the complex grid to look the way it was before I applied any exceptions.
But, it appears that the logic for $clearexceptions is to revert to the last "column" setting.
Click "Run Demo" to demonstrate.I spent a few hours trying to make use of the $fieldstyle property in complex grids only to reach the conclusion that it can't be done. Well, it can't be done for individual cells.
You can change the $fieldstyle for a complex grid column, but you can't change it for a single cell in a column.
Tech Support confirm this and suggested that this "feature" won't be changed.
To work around this problem, you need to "relax" the #STYLE restrictions on the $fieldstyle properties that you want to be able to change for individual cells in your complex grid. You can then modify the individual property, instead of trying to change the $fieldstyle.
Click "Run Demo" to demonstrate.If you have a reference to an object in a complex grid, how do you know which column of the
complex grid it is located in?
If you have a reference to an object in a complex grid, how do you know which section of the
complex grid it is located in?
The $gridsection property will tell you the answer. $gridsection returns the following
constants: kGridColumnHeader, kGridHeader, kGridNone, kGridOther, or kGridRow
To add columns to a complex grid you need to add a divider to the $dividers group.
Do $cinst.$objs.GRID.$dividers.$add(POSITION) Returns rDividerIf the complex grid $enterable property is set to kTrue, the user can no longer select lines the same way as in a headed list.
This tip shows how to add a simple $selected checkbox to the complex grid. Click "Run Demo" to demonstrate.Need a clean looking complex grid? Why not make it look like a spreadsheet?
This demo also provides code for having the up and down arrow keys moving vertical from cell to cell in the complex grid.
Click "Run Demo" to demonstrate.Data grids are handy for quickly showing all the columns and rows which are in a data list.
The downside on the data grid is that the data list definition must be exactly defined as you want to display the data. (The order and names of columns.)Adding a new line to a data grid is no problem.
Moving the cursor (the focus) to the newly added line in the data grid can be a challenge!
You can change the current cell in a data grid to another line by assigning the $gridvcell property.
There are 2 "tricks" you need to know about in order to move the cursor (focus) to the new line.
1. After adding the new line to the data list, you need to redraw the data grid so that the data grid will "have" the new line. (If you try to assign the $gridvcell to the last line before a redraw the data grid object doesn't know about the new line yet.)
2. If the data grid's $fixedrow property is true (shows column names header), then the $gridvcell value will be the data list line +1. If the $fixedrow property is false, the $gridvcell property will be equal to the data list line.You can drag and drop just about anything in Omnis Studio onto just about anything. If the $event method of the object supports "On evDrag" and "On evDrop" you can do it.
Under the Action properties tab, you must set the $dragmode to kDragData to allow dragging.
Under the Action properties tab, you must set the $multipleselect to kTrue to allow multiple line dragging.
Under the Action properties tab, you must set the $dropmode to kAcceptAll to allow dropping.
For some drag and drop operations you need to turn on $mousevents and use mouseover(). I always turn on $mousevents at the library level, negating the need to turn it on in the objects. If you are debugging a mouseover() be sure to calculate the mouseover value BEFORE any breakpoints. Calculate %L as Mouseover(kMLine) will be zero if you set you breakpoint before the Calculate %L.
You can take advantage of Studio's event message parameters to give you most of the information needed for drag and drop operations.
The various methods in the Drag and Drop demo are described in this section.
Run the demo to try out the various drag and drop operations.When I first started learning Omnis Studio, the 'Events and Messages' chapter in the manual was completely foreign to me. I skimmed through it comprehending very little. Eventually I wanted to add special behavior to some entry fields and was faced with attempting to understand this events handling business in Omnis Studio.
The Studio manual states that all user actions in Omnis generate an event. If the user clicks on a field in a window an event is generated.
My initial perception was that the entry field received the click and then reported it to Omnis Studio where it would be added to the event queue. I later discovered this was backwards.
THE BRICK AND THE HAND ILLUSTRATION
This following isnıt a perfect illustration, but work with me on this one anyway. Think of the entry field as your hand on top of a desk. Think of the mouse-click as being a brick falling from the sky, headed directly for your hand. Your eyes and nerves are the Omnis Studio event messaging system. Your brain is the $event method attached to the entry field (your hand).
Your eyes (the Omnis event messenger) see the falling brick and send a message to your brain (the $event method). Your brain makes a decision not to accept this event by instructing your hand to move out of the way. (Quit event handler, discard event)
Now on the other hand, if $100 of cash was falling from the sky, your brain (the $event method) may decide to leave your hand where it is and accept the event.
THE KEY
The key to creating an events-based application that properly functions is in the methods you write in the various objects in your library to intercept or handle those events.
So when you find event handling in Omnis Studio is misbehaving, the problem is likely in the code you've written. My experience has been it is usually my own misunderstanding of how event messaging is working in Omnis Studio.If you select a window object such as a pushbutton, then > F6 Property Manager > Action tab,
you will see the following properties for the window object
$keyevents (Keyboard events)
evKey events
$mousevents (Mouse Events)
evMouseDouble,evMouseDown,evMouseEnter,evMouseLeave,evMouseUp
$rmouseevents (Right Mouse Events)
evRMouseDouble,evRMouseDown,evRMouseUp
$statusevents (Status Events)
evDisabled,evEnabled,evHidden,evShown
Theses properties can be set to kTrue or kFalse.
If you > F2 Libraries, select a library > F6 Property Manager > Action tab, you will see the same properties for the library.
If you set $keyevents to kTrue for the library, the library setting takes precedence over the object settings. You can try to set $keyevents to kFalse for the object, but $keyevents event messages will still be sent to the object. This save you from having to think about setting it to kTrue for each object.
If you set $keyevents to kFalse for the library, you can set $keyevents to kTrue on an object by object basis.
To reduce the volume of event messaging Omnis gives you some pretty find tune control over event messaging.
I tend to set $keyevents and $mouseevents to kTrue for my libraries. So far I havenıt needed to trap right mouse or status events. Performance is not an issue.An object can be nested inside of a container field such as a tab pane object.
For discussion purposes we'll use a pushbutton which receives and passes an evClick event.
The evClick event is passed from the $event method of the pushbutton, to the $control method of the tab pane object. (Assuming a $control method is present).
If allowed to pass, the evClick event then passes to the $control method of the window class.
If allowed to pass, the evClick event then passes to the $control method of the task instance.
NESTED CONTAINER FIELDS
What happens if we put the pushbutton inside a group box, and then place the group box inside the
tab pane?
Omnis event handling makes creating context menus on the fly a snap.
This demo shows you how to add items to an empty context menu on the fly.
Items are added to the context menu in the $event method of the field and $control method of the window by calling the $addMenuItem method of the context menu.
The $control method of the window informs the context menu instance who to notify if user selects a context menu item by calling $AttachEventsObserver. For simplicity, this demo doesn't use the observer design pattern. The context menu simply store the observer window class instance and method to call in instance variables.
Go ahead and run the demo. Noisy OK messages have been scattered through the code so you can follow the sequence of events. You can hit Break (Win), or Cmd+Opt+Delete (Mac) at any OK message if you want to break in and look at the code.Debugging events can be a real pain. There is no way of looking at the event queue, and if you put in a breakpoint you mess up the event queue.
Putting a breakpoint after On evBefore will keep looping back each time you click the go button in the method editor because another evBefore event message will be generated each time the window comes to the front.
There are 2 techniques that I know of for debugging event handling problems:
SILENT DEBUGGING
Instead of OK messages you can send information to the trace log.
Send to trace log {[sys(85)] (Icon) {Event: [pEventCode]////Next event: [pNextCode]////More info.}
Then open the trace log and see what methods were called in what order.
NOISY DEBUGGING
OK messages do not affect the event queue, so you can place them in your various $event, $control, and other methods to figure out what methods are being called and in what order.
OK message [sys(85)] (Icon) {Event: [pEventCode]////Next event: [pNextCode]////More info.}
I tend to use OK messages for debugging events. The handy thing with OK messages is you can break into your code on an OK message by pressing Break (Win) or Cmd+Opt+Delete (Mac).Omnis event handling makes creating it easy to write drag and drop code.
This demo shows you how to copy items from one list to another, reorder them in a list, or delete them from a list.
All the code is in the $event methods of the objects.The event message is sent to the $event method of the object where the event occurred. (Report events are sent to the $print method.)
The $event method can choose to handle, pass, or discard the event.
An event will automatically pass to the $control method of an objectıs container under certain conditions.
Note: An object could be contained directly in a window class or nested inside a container object such as a group box. The objectıs container could be a window class instance or a group box depending where it is located.
An event can automatically pass up to the $control method of the task instance under certain conditions.
For discussion purposes weıll consider an evClick event on a pushbutton.
To handle the evClick event in the $event method of the pushbutton object an On evClick is required in the $event method.
The presence of the On evClick line of code will cause the event to be handled right then and there and not go any further. Even if there is no code after the On evClick.
The default entry fields in the component store have an $event method with On evBefore and On evAfter already in the code. This is likely done so that evBefore and evAfter events don't get passed up to the $control method.
PASSING EVENTS
There are several ways you can cause an event to pass to the $control method of its container.
1. Delete the $event method from the object. When Omnis sends the event message to the object, if
an $event method does not exist, the message is automatically sent to the $control method of the objectıs container.
2. Donıt include an On evEventName to handle the event. If the event isnıt handled by the $event method Omnis automatically passes the event to the $control method of the objectıs container.
3. Use If evClick instead of On evClick. If evClick will still trap the event, but it won't prevent Omnis from automatically passing the event to the next $control.
4. Handle the event but then pass it to the $control method using Quit event handler (Pass to next handler)ı. This doesnıt quit all event handling, it just quit event handling of the current event in that event handling method.
Note: The event message is sent to only ONE $event method and that is the object where the event occurred. After that it will ONLY be passed to $control methods. A window class can have a $event method and a $control method. It is easy to get confused and try to trap a window event like evOkay in the windowıs $control method. (I know from experience.)
WHY BOTHER ALLOWING EVENTS TO PASS TO $CONTROL?
You can save a lot of code by allowing events to pass to a $control method.
Say your have a toolbar with 10 buttons on it. You could have a $event method for each toolbar button, or you could allow the evClick events to pass to the $control method of the toolbar.
In the $control method, $cobj().$name tells you the name of the toolbar button which the use just clicked. If you are passing the toolbar button clicks to public methods by the same name in the top window, you use the following line of code to call those methods.User actions create an event. Events are reported in Omnis as event messages. An event message is sent to the $event method of the object where the event occurred. In many cases, even though the event has occurred the $event method of the object can cancel the event by discarding it.
(That would be like allowing the brick to hit your hand and then being able to move time back a few seconds and stopping the brick before it hits your hand.)
EVENT PARAMETERS
The event message has one or more event parameters. The first parameter is always pEventCode. Additional parameters tell you more about the event. (pNextCode, pDragField, pColumnNumber,)
The cool thing about event parameters is that they have task instance scope and you never have to (nor should you!) create them as parameter variables in your methods.
To prove the point, type Calculate #1 as pColumnNumberı in a new method. Notice that Omnis allows you to enter pColumnNumber even though you didnıt create it as a parameter in your method.
When an event is occurring any method can use the event parameters without declaring them and without having them passed to them. Remember, event parameters have current task scope.
You can find out what events can be handled for an object by double-clicking an object and going to its $event method and typing On in method editor. Omnis lists for you the possibe events that the selected object can handle.
You can find out what event parameters are available for any event by typing On evEventName (e.g. evClick) in your code, and with the On evClick line selected in the method editor press F9 > Variables tab > click Event Parameters in the left list. The possible event parameters will be shown in the right list. I say possibleı parameters, because not all event parameters listed are applicable to all objects. For the On evClick event the event parameters, pLineNumber and pRow only apply to list objects.
Note: You can drag and drop event parameter from the F9 Catalog into your code.
Note: If you have Help Tips turned on in your F9 Catalog you can hover over an event parameter on a tooltip will appear with more information about the event parameter. (Right-click anywhere the F9 Catalog to check the Help Tips context menu item. Then right-click > Save Window Setup)Omnis uses an event queue. There can be several events in the queue.
When you are in an entry field and you press the tab key there are several events that go into the queue.
evAfter will be sent to the field $event method
evTab will be sent to the field $event method
evBefore will be sent to the next field $event method
If you are in an entry field and click the close box on a window you will generate:
evAfter will be sent to the field $event method
evCloseBox will be sent to the window $event method
evClose will be sent to the window $event method
You can append events to the event queue using the Queueı set of 4GL commands.
Queue set current field {FIELDNAME}
... will cause Omnis to set the FIELDNAME as the current field after the other events in the queue have been processed.
Setting the current field will then trigger an evBefore event message to be sent to the $event method of FIELDNAME.
Omnis doesnıt let you tinker with events already in the event queue. I suspect Omnis is receiving the event messages from the operating system and therefore canıt be trying to take control of stuff going on inside the operating system.The key to writing an events-based application that properly functions is in the methods you write in the various objects in your library to intercept or handle those events. These methods are called event handling methods.
Event Messages are sent to the $event method of the objects.
The event messages contain event parameters. (pEventCode is the 1st parameter)
Use On ev... or pEventCode in your event handling methods to detect specific events.
Use event parameters to test the contents of the event messages.
Event parameters have current task ($ctask) scope. You do not need to (nor should you) create event parameters in your methods.
Event messages can be passed to the $control method of an objectıs container. If there are several layers of nested window object containers, the event message can pass to the first container, but then passes directly to the window $control method.
Event messages can be passed from the window $event or $control method to the $ctask.$control method.
Warning from the Studio Manual: ³The Omnis event processing mechanism gives you absolute control over what is going on in your application, but it also means you need to design your event handling methods with care. It is important not to pass an event to higher levels unnecessarily and to keep control methods short, to limit the time spent processing each event.²This 4GL command had me baffled for quite some time. I tried using it here and there and didnıt find any particular use for it.
The command has 2 possible forms:
Process event and continue
Process event and continue (Discard event)
PROCESS EVENT AND CONTINUE EXAMPLE
One situation where you would use this command is if you are allowing a user to drag an object in a window to reposition it. When they drop the object, you want to test if the new position is within the bounds of the window before deciding whether or not to accept the new position.
The windowıs $event method will receive an evDrop event message, but if you test the location of pDragField you will find it is the old position, not the new position because the evDrop event has not been processed yet. You have the opportunity to reject the event, leaving the object where it was.
To find out the new position you need to:
Process event and continue
then test the new position and decide if that is acceptable.
Of course now youıve got the problem that the event has been processed so no longer have the option of rejecting the event.
To work around that problem, in the window $event method you would:
Calculate Top as pDragField.$top
Calculate Left as pDragField.$left
Processs event and continue
Now if you want to reject the evDrop:
Do pDragField.$top.$assign(Top)
Do pDragField.$left.$assign(Left)
PROCESS EVENT AND CONTINUE (DISCARD EVENT) EXAMPLE
One example of processing an event and discarding it could be an evOK event message that you want to toss before opening a modal prompt window followed by Enter data. (Example from the Studio manual) Allowing the evOK event will cause Enter data to proceed as soon as the window opens. Quit event handler (Discard event) wonıt allow you to have event handling code after that line, so Process event and continue (Discard event) is the solution.
On evOKIf you want to discard or pass an event you can use the Quit event handler command to terminate an on construct. (Studio Manual)
The Quit event handler command has several formats:
1. Quit event handler
This stops your event handling code in the current method. The default action is processed immediately after this command. (I havenıt found a use for this format.)
2. Quit event handler (Discard event)
This stops your event handling code in the current method and discards the event. The event dies right then an there.
Note: If you are validating entry field data On evAfter and using Quit event handler (Discard event) on
incorrect data, you might want to test the pNextCode event parameter to see if the user is trying to close the window.
On evAfter
If pNextCode < > evCloseBox&pNextCode < >evStandardMenu
If $cobj.$contents=''
OK message {You must fill in a value.}
Quit event handler (Discard event)
End If
3. Quit event handler (Pass to next handler)
This stops your event handling code in the current method and passes the event to the next $control method in the event handling chain. The next $control method can decide to handle, pass, or discard the event.
I use this format if I want to do some processing of the event in the field and then allow the $control method to do additional processing of the event. An example might be adding menu items on the fly to a context menu. There might be some specific field menu items I want to add to the context menu, plus there might be some general window specific items I want to add to the context menu.
$event (field method)
On evOpenContextMenu pContextMenu.$addMenuItem(Field Specific Item 1ı)
Quit event handler (Pass event)
$control (window method)
On evOpenContextMenu
pContextMenu.$addMenuItem(Window Specific Item 1ı)
pContextMenu.$addMenuItem(Window Specific Item 2ı)
Quit event handler (Pass event)
The context menu has 3 items, 1 from the field, and 2 from the window. $addMenuItem is a custom method you add to the menu class. See Context Menus on-the-fly for more information.
4. Quit event handler (Discard event,Pass to next handler)
I donıt believe Omnis intended to make this combination possible. Correct me if Iım wrong but if the event is discarded there is nothing to pass to the next handler. Maybe there should be 3 radio buttons for Quit event handler? (None, Discard, Pass) Not a big issue, as long as we understand what is going on, weıll know not to try this combination.A subwindow field is a container field, but with a different twist.
A subwindow field contains a window instance within a parent window instance.
Lets say the subwindow window instance contains a pushbutton object. If the user clicks
the pushbutton, it receives an evClick event message, and if it allow the message to pass, the event message passes to the $control method of the subwindow window instance.
One might assume that if the event is allowed to pass again the next level it would pass to the $control method of the parent window, or the $control method of the task instance.
Wrong, and wrong again. The evClick event doesn't go anywhere further than the $control method of the subwindow window instance's $control method! This fact has caused a lot of head scratching on the part of the author.
I believe there was considerable OO logic that went into making this decision in the Omnis engineering department. The subwindow window class instance is not a full blooded window instance. You won't find it listed in the $iwindows group in the F4 Notation Inspector. So it doesn't have the 'right' to pass events to the current task $control method.
Following proper OO guidelines an object (the subwindow window instance) should not have to know anything about things outside of itself, therefore without someone explicitly informing the subwindow where to pass it's events, it can't make that decision on its own.
PASSING SUBWINDOW EVENTS
You could explicitly pass events from the subwindow window instance $control to the parent window $control event using:
Do $cwind.$control()
But be mindful, that you might be instantiating the same window class in another subwindow, or as a window instance on its own, and Do $cwind.$control() might not be ideal in every situation.
Note: As soon as you take over control by explicitly passing events, Omnis washes its hands and says "Fine, you're in charge now. I quit". This means that events you pass to $cwind.$control will NOT be passed by Omnis to $ctask.$control.
USING AN EVENTS OBSERVER
One interesting solution to passing events is to use the observer design pattern. With the observer design pattern a 'subjects' object is instantiated by the subwindow window class instance. Object(s) which want to receive event messages subscribe to the 'subjects' object. Whenever an event occurs in the subwindow window instance, the event is passed to the 'subjects' object which then notifies all the subscriber(s).
For more information on the Observer Design Pattern visit the free Downloads page at www.vencor.caTAB PANES
This section covers various topics related to tab panes.
I especially like using tab panes with subwindows. See Tab Pane Subwindows for more information.If you use the F4 Notation Inspector and click the magnifying glass on a tab pane object in design mode, then press the F6 Property Inspector and look under the General tab you will see the properties $alltabcaptions and $alltabtooltips.
Hovering over these properties with the mouse the tooltip explains that these properties return all a carriage return separated string of the tab captions or tooltips.
When I discovered this property I thought, "Great! We can assign the tabs caption without needing to lobject-oriented programming through the tab panes". Unfortunately $alltabcaptions and $alltabtooltips are not assignable and they are class properties and not class instance properties.
In my opinion, this renders these properties useless to the developer.
Click the "Run Demo" button for a demonstration.
Thanks to Philippe Sarfati for raising the $alltabcaptions question.Rather than set the tab pane captions manually, you can set them dynamically.
The tab pane in the StudioTips Browser window is set dynamically using notation.
This is accomplished by looping through a list which contains the tab pane captions. As you lobject-oriented programming through the list you need to set the $currenttab and then assign the $panecaption and other pane specific properties which you want to set.
The sample code below and demo show how to dynamically set the tab captions. Thinking in object-oriented terms, the method is named $setTabPaneCaptions and is added to the tab pane object. You then "send" a "$setTabPaneCaptions" message to the tab pane object passing the list of the tab pane captions as a parameter. To access this method from outside of the window class you would add a $setTabPaneCaptions window class method and redirect the call to the tab pane.
NOTE: Each time you set $currenttab an evTabSelected event is generated (if it results in changing the current tab). If you have code that runs when you change the current tab, you will want to temporarily stop the tab events from being generated while the tab pane captions are being set. A good trick is to temporarily set the tab pane object $active property to kFalse while setting the tab captions. Thanks to Brian O'Sullivan for this tip!I especialy like using tab panes with subwindows.
Tab1 might be the purchase order header and tab2 would be the purchase order items.
To improve window instantiation time, the $classname property of tab2,3,... is not assigned until the user clicks on the respective tab. This way when the window is intantiated only tab1's subwindow is instantiated.
The sample code and demo show you how this can be accomplished.
NOTE: If you wanted to create a flexible tab pane object you could have the tab pane add the subwindow field to itself using notation. You could create an instance variable list, iTabPaneList, which stores the tab caption names and the class name of each subwindow or an item reference to it. Then create a method, $addTabPaneSubWin(pTabNum,pTabCaption,prWin), which adds a new line to the iTabPanesList... and you've got yourself a very flexible reuseable object-oriented tab pane window which could be instantiated again and again.TreeList: a hierachical list with expandable and collapsable nodes.
TreeLists are among the last hold outs in my Omnis Studio learning experience. I remember attending a class on TreeLists at my first Omnis conference. After that session on TreeLists my thoughts were "TreeLists are too much work, I'll stick with headed lists, they are a lot easier".
TreeLists are more work for the programmer, but in the right situation they are a great GUI.
Imagine trying to navigate StudioTips without TreeLists! For presenting lots of information in a format that allows the user to explore the layers on their own, TreeLists are a great GUI.
This section of StudioTips covers all the TreeList methods and demonstrates what each one does.
Some of the methods, such as $countall, pertain the TreeList only. Other methods, such as $nodeparent, pertain the tree node only. Most methods, pertain to both the TreeList or a tree node.
The distinction between "Treelist methods" and "Treelist Node Methods" in the F1 Help is fuzzy at times. (It took me quite a while to figure out the correct syntax for $addbefore and $addafter.)
In this section if a method I try to make the distinction between treelist methods and treelist "node" methods. Methods pertaining only to the "kTreeColList" are appended with "_kTreeColList".
The naming conventions used in this section are as follows:
irTree ;; Item reference to the TreeList object. (instance variable)
irNode ;; Item reference to a node in the TreeList. (instance variable)
rNode ;; Item reference to a node in the TreeList. (local variable)
You do not need to issue a "redraw" after using a TreeList method. Each TreeList method automatically redraws the TreeList window object. (where applicable)
TREELIST DEMOS
For most of the demos, you can play around clicking on different nodes in the TreeList and then clicking the pushbutton to test the method on different nodes.
Holding down the shift key while clicking the TreeList method button in the demo window will take you to a breakpoint in the method code allowing you to step through the actual code so you can see what is happening behind the scenes.$add(cName[,iIdent,lRowForColumns])
Adds a node with the specified iIdent, and returns a tree node item reference to the new node. If the TreeList has multiple columns, you can supply the data for columns 2 and greater in the row variable lRowForColumns.
Add a new root level node to the TreeList.
Do irTree.$add(NodeName) Returns rNode
Do irTree.$add(NodeName,NodeIdent)
Do irTree.$add(NodeName,NodeIdent,ColumnsRow)
Add a new child node to "irNode". If any child node(s) already existing the new child node will be added to the end.
Do irNode.$add(NodeName)$addafter(rItem,cName[,iIdent,lRowForColumns])
Adds a node with the specified iIdent, after the node rItem, and returns a tree node item reference to the new node.
Add a new root level node after "irNode".
"irNode" can be a child node, but the new node will be added at the root level.
Do irTree.$addafter(irNode,NodeName) Returns rNewNode
Do irTree.$addafter(irNode,NodeName,NodeIdent) Returns rNewNode
Do irTree.$addafter(irNode,NodeName,NodeIdent,ColumnsRow) Returns rNewNode
Add a new child node to the parent "irNode" after the existing child node "rChildNode".
Do irNode.$addafter(rChildNode,NodeName) Returns rNewChildNode$addbefore(rItem,cName[,iIdent,lRowForColumns])
Adds a node with the specified iIdent, before the node rItem, and returns a tree node item reference to the new node.
Add a new root level node before "irNode".
"irNode" can be a child node, but new node will be added at the root level.
Do irTree.$addbefore(irNode,NodeName) Returns rNewNode
Do irTree.$addbefore(irNode,NodeName,NodeIdent) Returns rNewNode
Do irTree.$addbefore(irNode,NodeName,NodeIdent,ColumnsRow) Returns rNewNode
Add a new child node to the parent "irNode" after the existing child node "rChildNode"
Do irNode.$addbefore(rChildNode,NodeName) Returns rNewChildNode$clearallnodes()
Clears all nodes in the entire tree.
Do irTree.$clearallnodes()
Clears all nodes beneath the current node.
Do irNode.$clearallnodes()$collapse()
Closes all nodes in the entire TreeList.
Do irTree.$collapse()
Collapse "irNode".
Do irNode.$collapse()$count()
Return the number of root nodes in the tree
Do irTree.$count() Returns RootNodeNum
Return the number of nodes immediately below the current node
Do irNode.$count() Returns NodeNum$countall()
Returns the total number of nodes in the tree.
Do irTree.$countall() Returns NodeNum$currentnode()
Returns a tree node item reference to the currently selected node in the tree.
Do irTree.$currentnode() Returns irNode$edittext(iColumnNumber)
Lets the user edit the text for a column in the currently selected line, when the tree is the current field.
Do irTree.$edittext(ColNum)
The node (column 1) can only be entered if its $enterable property is kTrue. You can control whether other columns can be entered using the evTreeListEditStarting event.
Note: $edittext is only applicable the "kTreeColList" type TreeList. (I think?)
To stop columns from being enterable add this behind the tree:
-> On evTreeListEditStarting
-> Quit event handler (Discard event)
$edittext had me stumped for a while. The following is a question I asked to the list server and the kind response I received from David Barnett.
DOUG'S QUESTION:
I am currently working a TreeList section and demos for the next release of StudioTips. I'm stuck on the $edittext method.
According to the F1 help:
EDIT TEXT
$edittext(iColumnNumber) lets the user edit the text for a column in the currently selected line, when the tree is the current field. The node (column 1) can only be entered if its $enterable property is kTrue. You can control whether other columns can be entered using the evTreeListEditStarting event.
1. I don't fully understand the F1 Help explanation. Can anyone expand on this for me to make it a
bit clearer?
2. I can't find the $enterable property for the kTreeList window object. What tab is it hiding under, or do I have to set it some other way?
DAVID BARNETT'S RESPONSE:
If I remember correctly, controlling editing of Node Names is different than controlling editing of the other columns of a multi-column tree widget. Now I call it that because a tree object is NOT A LIST in any way that we normally think of an Omnis list or a window object which presents a list (like a complex grid).
The editing of Node names is not something you control in the Properties manager. It's a flag you set in the FlatList style list which you 'turn into' a tree (or add to a node). The flags in the flat list let you turn editing of node names on/off on a node by node basis.
As far as I could see, there's no property to 'turn off' the editing of the other tree columns.
All you can do is intercept the event which says you're starting to edit a tree column (not the Node name) and discard the event.
So, did I make that clearer or more obtuse?
David Barnett
Thanks David for clearing things up. :-)
So ... if you want to be able to enter data for any nodes in the TreeList, you must $setnodelist using kFlatList (or kTreeColList). In the column for $enterable, you set the node to kTrue in the list.
Run the $getnodelist demos to see the $enterable column in the data grid list views. See the Omnis Programming Manual page 155-156 for more information.$expand()
Open all nodes in the entire TreeList.
Do irTree.$expand()
Expand the current node.
Do irNode.$expand()$findident(iIdent)
Returns a tree node item reference to the node specified by iIdent
Do irTree.$findident(NodeIdent) Returns irNode
NOTE: I suspect $findident ONLY works for root level nodes.
DEMO: There is no demo for this TreeList method because I did not assign node idents to the TreeList when I built it.$findname(cName)
Returns a tree node item reference to the node specified by cName.
Do irTree.$findname(NodeName) Returns irNode
NOTE: $findname ONLY works for root level nodes.$findnodeident(rNodeRef,iIdent[,bRecursive=kFalse])
Searches the tree for a node which has the ident iIdent. If it finds such a node, it returns a tree node item reference to the node. Otherwise, it returns NULL. You can use the isnull function to test the returned item reference.
If you pass a tree node item reference in rNodeRef, then the method searches the nodes beneath the referenced node. To search the root nodes, pass zero as the rNodeRef parameter.
If you pass bRecursive as kTrue, then the search is recursive, searching the children of the nodes identified by rNodeRef, and so on.
Do irTree.$findnodename(0,NodeIdent) Returns rNode ;; Search the root nodes
Do irTree.$findnodename(irNode,NodeIdent) Returns rNode ;; Search the nodes below irNode
Search the root nodes. Recursive (search each branch).
Do irTree.$findnodename(0,NodeIdent,kTrue) Returns rNode ;; Search the root nodes
$findnodename(rNodeRef,cName[,bRecursive=kFalse,bIgnoreCase=kTrue])
Searches the tree for a node which has the name cName. If it finds such a node, it returns a tree node item reference to the node. Otherwise, it returns NULL. You can use the isnull function to test the returned item reference.
If you pass a tree node item reference in rNodeRef, then the method searches the nodes beneath the referenced node. To search the root nodes, pass zero as the rNodeRef parameter.
If you pass bRecursive as kTrue, then the search is recursive, searching the children of the nodes identified by rNodeRef, and so on.
The Boolean bIgnoreCase determines whether the search is case-sensitive.
Do irTree.$findnodename(0,NodeName) Returns rNode ;; Search the root nodes
Do irTree.$findnodename(irNode,NodeName) Returns rNode ;; Search the nodes below irNode
$first()
Return a tree node item reference to the first root node.
Do irTree.$first() Returns rNode
Return a tree node item reference to the first node immediately below the current node.
Do irNode.$first() Returns rNode$getcolumnalign()
Returns the alignment of the specified column. This only applies to trees with multiple columns.
Note: I believe "multiple columns" means only the "kTreeColList" type TreeList. See $setnodelist.
Do irTree.$getcolumnalign(ColNum) Returns kConstantAlign$getnodelist(iListmode,rNodeRef,lListname)
Returns the list data under the specified node or for the entire tree; iListmode can be kRelationalList, kFlatList or kTreeColList; rNoderef can be a tree node item reference or zero to retrieve the entire tree, lListname is the list variable to receive the list data.
Do irTree.$getnodelist(kRelationalList,irNode,List)
Do irTree.$getnodelist(kFlatList,irNode,List)
Do irTree.$getnodelist(kTreeColList,irNode,List)
66$getvisibleline(rItem)
Returns the line number of the node identified by the tree node item reference rItem, if the node is visible. If the node is not visible, it returns zero.
Do irTree.$getvisibleline(irNode) Returns LineNum$getvisiblenode(iVisLine)
Returns a tree node item reference to the node for the visible line iVisLine.
Do irTree.$getvisiblenode(3) Returns irNodeirNode().$level
$level is a node property which tells the indent level of the referenced node. See Node Properties for other properties.
Calculate NodeLevel as irNode().$level$multipleselect
If the $multipleselect property has been set to kTrue, the $multipleselect property of a selected node will be kTrue.
Setting the treelist property $multipleselect property to kTrue allows the user to select multiple nodes in the treelist.
The problem I ran into was trying to figure out which nodes the user has selected in a treelist. In a drag and drop operation the pDragValue give you a reference to the treelist...not much help in telling you which nodes are selected. I looked for a $selected node property but none existed. (At least not in v3.3) I turned to tech support who pointed me to the (undocumented) $multipleselect node property which returns kTrue if the node is selected.
The solution is to lobject-oriented programming through the visible nodes in the treelist and check their $multipleselect property. Any selected nodes will return kTrue.$nextnode(rItem,bRecursive)
Returns the node in the tree after the node identified by tree node
item reference rItem, or the first root node if rItem is zero; if bRecursive is kTrue the method
steps into any child nodes.
$nodeparent()
Returns a tree node item reference to the parent node of the current node. If the return value is empty, then the current node is a root node.
Do irNode.$nodeparent() Returns rNodeParent$prevnode(rItem,bRecursive)
Returns the node in the tree before the node identified by tree node item reference rItem; if bRecursive is kTrue, the method steps back into node parents.
Do irTree.$prevnode(irNode) Returns rNode$remove()
Delete the child node identified by tree node item reference rItem.
Do irTree.$remove(irNode)
Delete the child node identified by tree node item reference rItem. rItem must be a child of the current node.
Do irNode.$remove(rNode)Do irNode.$rowdata.$assign(Row)
You can assign the row variable value to the column using the TreeList node method $rowdata. Getting the data from the row is a bit tricky since you can only use $rowdata to assign the values.$setcolumnalign(iColumnNumber,Alignment)
Sets the alignment of the specified column. You can specify Alignment as kLeftJst, kRightJst, or kCenterJst. The property $columnalignmode controls how $setcolumnalign affects the field. This only applies to trees with multiple columns.
Note: I believe "multiple columns" means only the "kTreeColList" type TreeList. See $setnodelist.
Do irTree.$setcolumnalign(ColNum,kConstantAlign)$setcurrentnode(rItem)
Sets the currently selected node to the node identified by tree node item reference rItem. Note that this will only work for visible nodes, and that it will generate a click event on the node after making it current.
Do irTree.$setcurrentnode(irNode)
Do irTree.$setcurrentnode() ;; Leave blank to unselect node.$setnodelist(iListmode,rNodeRef,lListname)
Lets you populate a specified node or the entire tree with the data in lListname; iListmode can be kRelationalList, kFlatList or kTreeColList; rNoderef can be a tree node item reference or zero to populate the entire tree.
Do irTree.$setnodelist(kRelationalList,irNode,List)
Do irTree.$setnodelist(kFlatList,irNode,List)
Do irTree.$setnodelist(kTreeColList,irNode,List)
Do irTree.$setnodelist(kRelationalList,0,List) ;; Set the entire TreeList.
Do rNode.$tag.$assign(ANYTHING)
Omnis introduced a $tag treelist node property which can be assigned by the developer. The $tag property is a binary, so you can assign any datatype to the $tag node property.
I have found the $tag node property great for building treelists on the fly. For example if I build a treelist of all the classes in a library, as the list is built, I assign a class item reference to each node's $tag property. When the user clicks to expand a class node, I simply use $tag property to get the item reference to the class. With the class reference in hand, a list of methods for that class can be very quickly built and the method name nodes added. As each method node is added, the method item reference is assigned to the node's $tag property.
Use your imagination. The $tag property can help with building treelists on the fly and cut a lot of code.Omnis added the ability to display columnar list type data with the TreeList object.
The "kTreeColList" list is the same as the "kFlatList" list with the exception of an extra column. The extra column is a row variable and is the 5th last column in the kTreeColList. You can have as many columns in the row as you like.
See "$rowdata_kTreeColList" for more information.
COLUMN TREELIST DEMO
The column TreeList "Run Demo" with this tip was provided by Tech Support. I just modified some of the naming conventions and added the Get Row Data code to it. Feel free to look behind the TreeList at the kTreeColList code.Each node has a number of properties which are helpful when writing TreeList methods. Some can be assigned, others are read ony.
irNode().$PROPERTY
$name ;; Visible name of the node
$level ;; Indent level of the node
$nodeparent ;; Reference to the node's parent
$first ;; Reference to the nodes first child node
$iconid ;; Icon ID for the node.
$ident ;; Ident for the node.
$seedid ;; Unique number assigned to each node by the tree
$textcolor ;; color the node name is drawn in, if kColorDefault, the tree control's $textcolor is used.
$enterable ;; if true, the node name can be edited in the tree.
$isexpanded ;; true for nodes which are in the expanded state.
$checked ;; if true node icon is drawn in the checked state. Also depends on $treenodeiconmode.
$showexpandalways ;; if true, the node will always draw an expand/collapse box
;; useful for populating a node on evTreeExpand event
$rowdata ;; can be used to assign the row variable for the kTreeColList type TreeList
$multipleselect ;; if the $multipleselect property has been set to kTrue, the $multipleselect property of a selected node will be true.
Calculate NodeLevel as irNode().$PROPERTYYou can populate the TreeList from a list variable using the $setnodelist method. You can either populate the entire TreeList, or just a node in the TreeList.
The 3 ways of populating a treelist from a list are:
kRelationalList ;; Simplest form.
kFlatList ;; Lets you specify the node properties.
kTreeColList ;; Lets you specify the node properties and display column(s).
See the Omnis Programming manual page 155-156 for more details.
See $setnodelist and $getnodelist in the TreeList methods below for more details.
POPULATING ON-THE-FLY
After working for a while with populating treelists from lists I switched to building treelists on-the-fly. Typically I just build the root level nodes with the expand node icons. When the clicks to expand a node I trap the evTreeExpand and using pNodeItem find out which node is being expanded, clear the child nodes and add the child nodes one at a time in a For lobject-oriented programming. As each node is added you use the node reference returned to set the node properties. Populating treelists on the fly is fast, flexible, and in my opinion less work than populating them from lists.In StudioTips I needed to be able to store and restore the exact "state" of each TreeList which you look at in the StudioTips Browser. That way if you close the Browser window or even close the TIPS library, when you reopen the window all the TreeLists will be reopened in the same "state" that you last looked at them.
Tech Support gave me the solution. You simply use the parameter #NULL in the $getnodelist and $setnodelist methods.
The code I use is as follows:There are a number of events related to TreeLists. You can trap them in the $event method of the TreeList.
evTreeCollapse ;; indicates a node is about to be collapsed and provides the reference in "pNodeItem"
evTreeExpand ;; indicates a node is about to be expanded and provides the reference in "pNodeItem"
evTreeExpandCollapseFinished
evTreeNodeIconClicked ;; indicates a node has been clicked and provides the reference in "pNodeItem".
evTreeListEditStarting ;; pColumnNumber,pNodeItem
evTreeListEditFinishing ;; pColumnNumber,pNodeItem,pNewText
evTreeListEditFinished ;; pColumnNumber,pNodeItem
evTreeNodeNameFinished ;; pNodeItem,pNewText
evTreeNodeNameFinishing ;; pNodeItem,pNewTextLocate buttons along the bottom of the window.
Right justify the buttons along the bottom.
Okay/Default button is always furthest to the right.
Cancel/No button is next to the left.
Other buttons are to the left of the Cancel/No button.
Center justify the text in buttons. Use plain text.":"
The industry standard has been to append a colon ":" on the end of every label. I feel that is an old habit from the 'command line' green screens. Going against the industry standard, I do not append colons on my labels.Always use ellipsis on Menu and Buttons when another window is opened which will require user input. (A prompt window)
Do NOT use ellipsis if the command simply opens another window.Write code to avoid the need for them. :-)
Error messages viewed by the user must be in user friendly language. Error messages must not simply state the problem. They must help the user to solve the problem. A further level of information for the programmer should be available. Using a window expander triangle?I would like to indicate to the user which field has the focus. The plan is to change the field border color/style on the field with focus to a suitable color. (Blue?)
I plan to implement a field events handler to do this. The field events handler will be called by the $control method of the windows to avoid a lot of code in the individual fields.Be VERY consistent with the use of fonts throughout the entire application.
UPPER CASE, LOWER CASE, MIXED CASE
LABELS, MENUS, BUTTONS
Use capitalized words, except short words like: and, or, from, to, of, a, as, an
If a menu has a matching toolbar button, include the same icon in the menu. This helps user learning. Create shortcut combination keys for commonly used menu items.
Provide keyboard equivalents for the wintel platform. (The unique letter underlining thing). I admit to not yet having done this latter item in my application.The shortform for "Okay" is "OK". (not "Ok")
The shortform for "Identification" is "ID". (not "Id")Toolbar buttons are a great User Interface.
Toolbar buttons should not be a mirror of the menu items. Toolbar buttons should be for higher frequency use commands.I don't have a lot of experience or expertise in designing a proper User Interface.
This section is just a collection of ideas and guidelines which I have been collecting along the way. I've been reading a couple books on User Interface and am using this section for saving my reference notes. Don't take the information in this section too seriously. These are not guidelines for everyone. Read and decide for yourself.Users tend to read a window from top left to bottom right. The top left and bottom right get good attention from the users. The "sweet spot" is dead center.
The top right and bottom left get the least attention from the users. If you use graphics, (or less important info) put it in the top right and bottom left corners.
Thanks to Mischa Klement for his 'User Psychologyı session at Euromnis 2000.