Tips_wip   >   Windows   >   Windows
weThe 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.
; "rField" is an item reference variable referencing a multi-line entry field.
; The field is displaying some text.
; rField.$add(21) returns the number of lines in the multi-line field. (Undocumented feature)
Calculate Linecount as rField.$add(21)
; Assign the negative of the linecount to the $height correctly sets the multi-line field height. (Undocumented feature)
Do rField.$height.$assign(-Linecount)
; If you wanted to accomplish the above in a single line of code. :-)
Do rField.$height.$assign(-rField.$add(21))
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 loop
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 loop. (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 loop ...
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.