home *** CD-ROM | disk | FTP | other *** search
-
- ΓòÉΓòÉΓòÉ 1. Title Page ΓòÉΓòÉΓòÉ
-
- Welcome to EDM/2 - The Electronic OS/2 Developers Magazine!
- Portions copyright (c) by Steve Luzynski, Larry Salomon Jr.
- Volume 1, issue 4
-
-
- ΓòÉΓòÉΓòÉ 2. Copyright Notice (and other Legal Stuff) ΓòÉΓòÉΓòÉ
-
- The editors of this electronic magazine are Steve Luzynski and Larry Salomon,
- Jr.
-
- Portions of EDM/2 are copyrighted by the editors. This publication may be
- freely distributed in electronic form provided that all parts are present in
- their original unmodified form. A reasonable fee may be charged for the
- physical act of distribution; no fee may be charged for the publication itself.
-
- All articles are copyrighted by their authors. No part of any article may be
- reproduced without permission from the original author.
-
- Neither this publication nor the editors are affiliated with International
- Business Machines Corporation.
-
- OS/2 is a registered trademark of International Business Machines Corporation.
- Other trademarks are property of their respective owners. Any mention of a
- product in this publication does not constitute an endorsement or affiliation
- unless specifically stated in the text.
-
-
- ΓòÉΓòÉΓòÉ 3. From the Editors ΓòÉΓòÉΓòÉ
-
- Well, after a serious hiccup in the release schedule, we are finally putting
- out our next issue. We is used because the founder and original
- Editor-in-Chief, Steve Luzynski, found himself with too much work and not
- enough time, so another editor was added (Larry Salomon), and he temporarily
- assumed the duties as Editor-in-Chief until Steve can return in full capacity.
-
- Because the original July issue never made it out as far as I know, I've tried
- to put together an issue containing material that I wrote and any articles that
- I received via the Internet after the ballyhoo about Steve's untimely (and
- hopefully temporary) demise. My apologies if this issue appears to be a "Larry
- Salomon-only" edition, but - hey - you have to work with what you have to work
- with.
-
- Andre Asselin, whom you'll remember was writing the article series on IFS
- Development, regretfully told us that he would not be able to meet our deadline
- for this issue. He added that he will try to continue it in the next issue,
- fortunately.
-
- At the time of this writing, we were unable to reach Gavin Baker, the writer
- for the Introduction to PM Programming column. While we are continuing our
- efforts in this area, we appreciate any contact that can be established by our
- readers.
-
- Finally, our thanks and one box of Scooby Snacks go to the following people who
- provided administrative support for this issue (usually in the form of
- pre-release reviews):
-
- o Mir Assadullah
-
- Enjoy!
- The Editors
-
-
- ΓòÉΓòÉΓòÉ 4. This Month's Features ΓòÉΓòÉΓòÉ
-
- The following articles constitute this month's features:
-
- o C++ Encapsulation of PM
- o Development of a New Window Class - Part 1
- o Programming the Container Control - Part 2
-
-
- ΓòÉΓòÉΓòÉ 4.1. C++ Encapsulation of PM ΓòÉΓòÉΓòÉ
-
- Written by Gordon W. Zeglinski
-
-
- ΓòÉΓòÉΓòÉ 4.1.1. Introduction ΓòÉΓòÉΓòÉ
-
- The basic goal of encapsulating the PM interface is relating the non-C++ based
- instance information into a C++ based form. Specifically, in PM, each window
- has its own unique handle. In C++, each instance of a window would have its
- own unique this pointer. Thus, the goal in encapsulating PM, at the simplest
- level, is to relate the handle passed to the window procedure to the C++
- instance.
-
- There are two methods by which the C++ "version" of the window can be
- associated with the PM "version" which are discussed below, followed by a
- detailed look at the implementation for one of these two methods. This purpose
- of this article is to promote the usage of the objects described here; it is
- merely to illustrate some of the concepts of PM encapsulation and some of the
- methods that can be used to implement this encapsulation.
-
- Having said this, the objects illustrated here are only part of my collection
- of OS/2 objects, which are used in other programming projects. The primary
- design consideration behind this object hierarchy is to eliminate as much
- tedium as possible with a minimization in complexity and performance
- degradation. Also, the development of this library is not my primary goal; the
- library grows as I need more power or features in my other programs.
-
- Given sufficient interest, future articles could be written detailing the
- encapsulation of other controls, thread manipulation in C++, and extending the
- object hierarchy.
-
-
- ΓòÉΓòÉΓòÉ 4.1.2. Encapsulation Strategies ΓòÉΓòÉΓòÉ
-
- Method 1
-
- PM allows the application to allocate additional storage space that the
- application can use to store window instance related data in (called window
- words). The first method of window encapsulation would use this additional
- storage space to hold the this information.
-
- The problem with this approach is that in order to access this extra storage
- space, the application must know the window's handle. Generally, this is not a
- problem but the C++ dispatch function has to rely on having a valid pointer.
- The first opportunity the application has to validate the pointer is not until
- the WM_CREATE message is received. More importantly, while creating new
- windows, the application cannot assume that any message received has a valid
- pointer until the window being created has received the WM_CREATE message.
- During this time, the application must rely on method 2 to dispatch the
- messages to the appropriate window instance.
-
- Method 2
-
- This method does not use any additional storage space allocated by PM. Instead,
- the application maintains a list of handles and their corresponding instances
- and window procedures. This is the approach taken here.
-
-
- ΓòÉΓòÉΓòÉ 4.1.3. Support Objects ΓòÉΓòÉΓòÉ
-
- The class hierarchy can be divided into two principle sections: 1) the objects
- that are used to support the windowing objects and 2) the window objects
- themselves. The support objects will be described first so that their usage
- can be seen when the window objects are detailed later. Some of these objects
- merely encapsulate individual data items used by PM into single objects while
- others implement the primary message dispatching functions.
-
- PMainThread
-
- This class is used to register the thread with PM and to create its message
- queue.
-
- class PMmainThread {
- protected:
- HAB hab;
- HMQ hmq;
-
- public:
- PMmainThread()
- {
- //----------------------------------------------------------------
- // Either of the two indicate an error has occurred.
- //----------------------------------------------------------------
- if ((hab = WinInitialize(0)) == NULLHANDLE)
- exit(1);
- if ((hmq = WinCreateMsgQueue(hab,0)) == NULLHANDLE)
- exit(1);
- }
-
- virtual ~PMmainThread()
- {
- WinDestroyMsgQueue(hmq);
- WinTerminate(hab);
- }
-
- BOOL MessageLoop(void);
- HAB GetHAB() { return hab; }
- HMQ GetHMQ() { return hmq; }
- };
-
- Also, this class provides a generic message loop that can be used by the
- application.
-
- MessageStruct
-
- This class simply encapsulates the message parameters sent by PM into a single
- structure.
-
- struct MessageStruct {
- BaseWindow *TheWin;
- ULONG iMessage;
- MPARAM mParam1;
- MPARAM mParam2;
-
- MessageStruct(BaseWindow *TW,ULONG iMess, MPARAM mP1, MPARAM mP2) {
- TheWin=TW;
- iMessage=iMess;
- mParam1=mP1;
- mParam2=mP2;
- }
- };
-
- Message Dispatch Objects
-
- WinEventHandler
-
- It is desirable to have a single generalized dispatch object that can be
- referred to by a pointer which would then hide the multi-threading and message
- dispatching from the rest of the code. While it would be extremely difficult
- for a single object to do all this, a hierarchy of objects can be used instead.
- The parent of this hierarchy follows:
-
- class WinEventHandler {
- public:
- static const char Action_Chain;
- static const char Action_CallDef;
-
- WinEventHandler() {};
-
- HAB GetAppHAB() { return (BaseWindow::App)->GetHAB(); }
- virtual HAB GetHAB()=0;
- virtual void SetHAB(HAB hb) {}
-
- virtual int MultiThread()=0;
- virtual MRESULT Run(MessageStruct &Msg,char &Action)=0;
- };
-
- The above class provides a generic interface to objects which actually
- implement the code necessary to dispatch a message in a transparent manner. It
- should be noted that it is an abstract class.
-
- WinNewEventHandler (template)
-
- Generally, since all derived classes need to implement the pure virtual
- functions of their parent abstract class, object class templates are used to
- allow the message handling function to be a non-virtual member function. The
- template below assumes that the message dispatch function belongs to the class
- of the window object and it additionally specifies that a new thread should be
- created to process the messages.
-
- template<class C1> class WinNewThreadHandler: public WinEventHandler {
- HAB hab;
- MRESULT (C1:: WinProc)(WinEventHandler &, MessageStruct &, char &);
-
- public:
- WinNewThreadHandler(MRESULT (C1::* Proc)(WinEventHandler &,
- MessageStruct &,
- char &)): WinEventHandler() {
- WinProc=Proc;
- }
-
- virtual int MultiThread() { return 1; }
- virtual MRESULT Run(MessageStruct &Msg,char &Action) {
- return (((C1 *)(Msg.TheWin))->*WinProc)(*this,Msg,Action);
- }
-
- virtual HAB GetHAB() { return hab; }
- virtual void SetHAB(HAB hb) { hab=hb; }
- };
-
- WinSameEventHandler (template)
-
- This object is similar to the object WinNewThreadHandler in every aspect except
- that the message processing function is dispatched in the same thread as the
- primary message dispatch function.
-
- template<class C1> class WinSameThreadHandler: public WinEventHandler {
- MRESULT (C1::* WinProc)(WinEventHandler &,
- MessageStruct &,
- char &);
- public:
- WinSameThreadHandler(MRESULT (C1::* Proc)(WinEventHandler &,
- MessageStruct &,
- char &)): WinEventHandler() {
- WinProc=Proc;
- }
-
-
- virtual int MultiThread() { return 0; }
- virtual MRESULT Run(MessageStruct &Msg,char &Action) {
- return (((C1*)(Msg.TheWin))->*WinProc)(*this,Msg,Action);
- }
-
- virtual HAB GetHAB() { return GetAppHAB(); }
- };
-
- WinNewEventHandlerDifInst (template)
-
- In some cases, it is desirable to have the function which processes the message
- belonging to an object type different than that of the window. The following
- object allows this type of scenario to occur in a multi-threaded nature.
-
- template<class C1> class WinNewThreadHandlerDifInst: public WinEventHandler {
- HAB hab;
- C1 *TheClass;
-
- MRESULT (C1::* WinProc)(WinEventHandler &,
- MessageStruct &,
- char &);
- public:
- WinNewThreadHandlerDifInst(C1* Inst,
- MRESULT (C1::* Proc)(WinEventHandler &,
- MessageStruct&,
- char &)): WinEventHandler() {
- TheClass=Inst;
- WinProc=Proc;
- }
-
- virtual int MultiThread() { return 1; }
- virtual MRESULT Run(MessageStruct &Msg,char &Action) {
- return (TheClass->*WinProc)(*this,Msg,Action);
- }
-
- virtual HAB GetHAB() { return hab; }
- virtual void SetHAB(HAB hb) { hab=hb; }
- };
-
- WinSameEventHandlerDifInst (template)
-
- Like the above, this object allows mixing of object types. The difference here
- is that the message is processed in the same thread that it is dispatched.
-
- template<class C1> class WinSameThreadHandlerDifInst: public WinEventHandler {
- C1 *TheClass;
- MRESULT (C1::* WinProc)(WinEventHandler &,
- MessageStruct &,
- char &);
- public:
- WinSameThreadHandlerDifInst(C1* Inst,
- MRESULT (C1::* Proc)(WinEventHandler &,
- MessageStruct &,
- char &)): WinEventHandler() {
- TheClass=Inst;
- WinProc=Proc;
- }
-
- virtual int MultiThread() { return 0; }
- virtual MRESULT Run(MessageStruct &Msg,char &Action) {
- return (TheClass->*WinProc)(*this,Msg,Action);
- }
-
- virtual HAB GetHAB() { return GetAppHAB(); }
- };
-
- Handler Thunks
-
- To create a thread under Borland C++ for OS/2 or IBM Cset++, one has to use the
- function _beginthread(). Note that the function has different parameters in
- the two compilers. This function does not make any provision for starting
- threads on C++ member functions, so a helper function is needed. The helper
- function takes an object of the following class as an argument and dispatches
- the multi-threaded message handler by issuing a call to
- (TheHandler->run)(*TheMessage,temp).
-
- struct HandlerThunkData {
- WinEventHandler *TheHandler;
- MessageStruct *TheMessage;
-
- HandlerThunkData(WinEventHandler * TH,MessageStruct * TM) {
- TheHandler=TH;
- TheMessage=TM;
- }
- };
-
- WindowInfo
-
- This class is used to relate the window handle to the corresponding C++ window
- instance.
-
- struct WindowInfo {
- HWND hwnd;
- BaseWindow *THIS;
-
- WindowInfo(HWND hw, BaseWindow * BW) {
- hwnd=hw;
- THIS=BW;
- }
- };
-
- InitializationData
-
- Objects of this class are passed to the window procedures so that when the
- WM_INITDLG or WM_CREATE message is received, the window event handler can be
- registered enabling it to process these messages.
-
- struct InitializationData {
- BaseWindow *BW;
- WinEventHandler *Event;
-
- InitializationData(BaseWindow *B, WinEventHandler *E) {
- BW=B;
- Event=E;
- }
- };
-
-
- ΓòÉΓòÉΓòÉ 4.1.4. Window Objects ΓòÉΓòÉΓòÉ
-
- Because the common denominator between all type of windows in PM is the window
- handle, we will begin by creating a generic window object which encapsulates
- the translation of the window handle to the instance pointer of the C++ object.
- This generalized class also stores the the window's handle.
-
- In PM, nearly every window consists of a frame window and the client window.
- This is so common, in fact, that a special function - WinCreateStdWindow -
- exists in PM to create the frame and client windows simultaneously. However, a
- frame window is merely an instance of the window class WC_FRAME allowing us to
- create it seperately using the WinCreateWindow function. Because of this, our
- C++ objects can be separated into frame windows and client windows.
-
- The window hierarchy is based upon the object BaseWindow. Since this seems to
- be the logical place to start, we will first describe this class.
-
- BaseWindow
-
- class BaseWindow {
- protected:
- static PApplication App; // Pointer to Primary window thread object
- static ULONG NumWindows; // Each instance has a handler
- static ULONG MaxNumWindows; // list
- static WindowInfo **Instances;
-
- USHORT NumHandlers;
- USHORT MaxNumHandlers;
- WinEventHandler **Handlers;
-
- HWND hWndFrame; // Window handle
- HWND hWndParent;
-
- PSZ pszClassName; // Window-class name
- ULONG flClassStyle; // Default-window style
- USHORT usExtra; // Reserved storage
-
- static MRESULT EXPENTRY _export PrimWndProc(HWND hWnd,
- ULONG iMessage,
- MPARAM mParam1,
- MPARAM mParam2);
-
- static void AddWindow(BaseWindow *win, HWND hand);
- static void RemoveWindow(BaseWindow *win, HWND hand);
-
- public:
- BaseWindow();
- BaseWindow(PSZ ClassName, ULONG ClassStyle, USHORT Extra, HWND Parent);
- BaseWindow(PSZ ClassName, HWND Parent=HWND_DESKTOP);
-
- virtual ~BaseWindow();
-
- void Show();
- void Hide();
- void Close();
-
- static void SetApplication(PApplication applic) {
- App=applic;
- }
-
- static PApplication GetApplication() { return App; }
- void AddStyle(ULONG style) { flClassStyle|=style; }
-
- BOOL Register() {
- return WinRegisterClass(App->GetHAB(),
- pszClassName,
- &PrimWndProc,
- flClassStyle,
- usExtra);
- }
-
- HWND GetHandle() { return hWndFrame; }
- HWND GetParentHandle() { return hWndParent; }
-
- void ChangeIcon(ULONG Icon);
-
- void AddHandler(WinEventHandler *Event);
-
- BOOL PostMessage(ULONG iMess, MPARAM mP1=0, MPARAM mP2=0) {
- return WinPostMsg(hWndFrame,iMess,mP1,mP2);
- }
-
- MRESULT SendMessage(ULONG iMess, MPARAM mP1=0, MPARAM mP2=0) {
- return WinSendMsg(hWndFrame,iMess,mP1,mP2);
- }
-
- //-------------------------------------------------------------------
- // In CSET, a static member function cannot be used as a window
- // procedure thus they are friends here. But above, the static
- // functions are used in BC4OS2
- //-------------------------------------------------------------------
- friend MRESULT EXPENTRY PrimWndProc(HWND hWnd,
- ULONG iMessage,
- MPARAM mParam1,
- MPARAM mParam2);
-
- friend MRESULT EXPENTRY PrimDlgProc(HWND hWnd,
- ULONG iMessage,
- MPARAM mParam1,
- MPARAM mParam2);
-
- friend WinEventHandler;
- friend class ModalDlgBox;
- };
-
- Now let's look at exact role this class fills. The data objects and member
- functions
-
- o NumWindows
- o MaxNumWindows
- o Instances
- o PrimWndProc
- o AddWindow
- o RemoveWindow
-
- store and manipulate the list of created windows. The window information is
- stored in an array which is unsorted and traversed by the function PrimWndProc
- each time a message is received. When the handle is matched, the instance of
- the window is known. At this time, the list of registered window handlers is
- traversed.
-
- The following data objects and functions are used to manipulate the list of
- registered handlers:
-
- o NumHandlers
- o MaxNumHandlers
- o Handlers
- o AddHandler
-
- In summary, the window instance is registered using the function AddWindow.
- Message handlers are registered using the function AddHandler. Each time a
- message is received, the list of registered windows is traversed. If the
- window handle is found, the list of handlers corresponding to the instance
- found is traversed and each message handler is allowed to process the message
- until one of them block further processing.
-
- ModalDlgBox
-
- The dialog window creation process can also be encapsulated. The following
- class encapsulates the process of creating a modal dialog window contained in
- the application's resources. A similar class exists for modeless dialog
- windows. Also, classes exist for both type of dialog windows with menus.
-
- The function PrimDlgProc is identical to the function PrimWndProc except that
- this function calls the default dialog procedure for unprocessed messages and
- looks for the WM_INITDLG message whereas the latter calls the default window
- procedure and looks for the WM_CREATE message.
-
- class ModalDlgBox: public BaseWindow {
- protected:
- ULONG RetVal;
-
- static MRESULT EXPENTRY _export PrimDlgProc(HWND hWnd,
- ULONG iMessage,
- MPARAM mParam1,
- MPARAM mParam2);
-
- public:
- ModalDlgBox(BaseWindow *Owner,
- ULONG ResID,
- HMODULE ResMod=NULLHANDLE,
- HWND Parent=HWND_DESKTOP);
-
- ModalDlgBox(BaseWindow * Owner,
- ULONG ResID,
- WinEventHandler *Event,
- HMODULE ResMod=NULLHANDLE,
- HWND Parent=HWND_DESKTOP);
-
- ModalDlgBox(HWND Owner,
- ULONG ResID,
- HMODULE ResMod=NULLHANDLE,
- HWND Parent=HWND_DESKTOP);
-
- ModalDlgBox(HWND Owner,
- ULONG ResID,
- WinEventHandler *Event,
- HMODULE ResMod=NULLHANDLE,
- HWND Parent=HWND_DESKTOP);
-
- ULONG GetReturn() { return RetVal; }
- BOOL Dismiss(ULONG x){ return WinDismissDlg(hWndFrame,x); }
-
- HWND GetControlID(ULONG ID) {
- return WinWindowFromID(hWndFrame,ID);
- }
-
- friend class ModlessDlgBox;
- };
-
- FrameWindow
-
- Following is a generic frame window class. This class is usable in this form
- but one still has to provide the various control flags and such as paramters.
- It would be a trivial task to derive subclasses that specify the various frame
- creation options to this object without requiring them to be passed to their
- constructors as paramters.
-
- The frame window (PM class WC_FRAME) is created by this object using the
- WinCreateWindow function.
-
- class FrameWindow: public BaseWindow {
- FRAMECDATA fcdata;
- ULONG flFrameControlFlags;
- PSZ Title;
-
- public:
- FrameWindow();
- FrameWindow(PSZ pTitle,
- ULONG ControlFlags,
- HMODULE ResMod,
- ULONG ResID,
- HWND ZOrder=HWND_TOP,
- ULONG IDNum=1,
- HWND Parent=HWND_DESKTOP);
- FrameWindow(PSZ pTitle,
- ULONG ControlFlags,
- HMODULE ResMod,
- ULONG ResID,
- LONG XPos,
- LONG YPos,
- LONG Height,
- ULONG Width,
- HWND ZOrder=HWND_TOP,
- ULONG IDNum=1,
- HWND Parent=HWND_DESKTOP);
-
- ULONG GetFlags() { return flFrameControlFlags; }
- };
-
- ClientWindow
-
- This object serves as the base from which client windows are derived. This
- object simply encapsulates the data and method of interacting with the object
- FrameWindow. Note a bug in BC4OS2's virtual function tables prevents this
- object to work properly. A work around is done in this code but it will be
- changed once I get the product-level C/Set++.
-
- class ClientWindow :public BaseWindow {
- FrameWindow *ParentWin;
-
- public:
- static const char NoCreateWin;
- static const char CreateWin;
-
- ClientWindow(FrameWindow* Parent,
- PSZ ClassName,
- ULONG ClassStyle,
- USHORT Extra,
- char Create=CreateWin);
-
- /* BUG in BC virtual table (corrupt)...trying the following...
- virtual BOOL IsRegistered()=0;
- virtual void SetRegistered()=0;
- */
-
- virtual BOOL IsRegistered() { return 0; }
- virtual void SetRegistered() {};
- };
-
- Example Client Window Subclass
-
- Following is a simple client window that illustrates the prodecure of creating
- new client window types. The client object must keep track of whether it has
- been previously registered to PM via WinRegisterClass so that it is not
- re-registered.
-
- class SimpClient: public ClientWindow {
- static BOOL Registered;
- WinEventHandler *Event;
-
- public:
- SimpClient(FrameWindow *Frm);
- ~SimpClient();
-
- virtual BOOL IsRegistered() { return Registered; }
- virtual void SetRegistered() { Registered=1; }
-
- MRESULT MessageHandler(WinEventHandler &hand,
- MessageStruct &Msg,
- char &Action);
- };
-
- The static data member Registered is used to keep track of whether it has been
- previously registered or not. The data member Event is intialized in the
- constructor with :
-
- Event=new WinSameThreadHandler<SimpClient>(&SimpClient::MessageHandler);
-
- The function MessageHandler is the message handler for this object.
-
-
- ΓòÉΓòÉΓòÉ 4.2. Development of a New Window Class - Part 1 ΓòÉΓòÉΓòÉ
-
- Written by Larry Salomon, Jr.
-
-
- ΓòÉΓòÉΓòÉ 4.2.1. Introduction ΓòÉΓòÉΓòÉ
-
- This article begins a multi-part series which will ultimately result in the
- development of a new window class. Everything from the functional requirements
- to the design to the implementation will be described, giving you a good look
- at how it is developed from the ground up; in the end, we'll have a completed
- window class with a variety of uses.
-
- Inspiration
-
- Everyone can remember back to their days in middle school when three-ring
- binders were all-the-rage and your parents purchased many packs of paper to
- fill them. The holes in the paper and the rings in the binders combined to
- allow for easy transfer to other notebooks or for inscription on the paper
- prior to storage in a binder.
-
- Now we're in the computer age, and paper is quickly becoming a thing of the
- past. However, direct translations of computer concepts to the "real world" is
- now the thing to do; so, wouldn't it be nice to have a piece of loose-leaf on
- your monitor to scratch notes on?
-
-
- ΓòÉΓòÉΓòÉ 4.2.2. Functional Requirements ΓòÉΓòÉΓòÉ
-
- Before we can do anything, we need to first define what our goals are going to
- be. What will our control do from the user's perspective? What should it look
- like? What capabilities will it have from a programming standpoint? These are
- not easy questions to answer, nor do they have answers that are correct.
- Because the nature of these questions is subjective, the best answers are going
- to be the ones that address the issues of those people that are most affected.
-
- What will our control do from the user's perspective?
-
- Since the term "loose leaf" conjures up specific expectations about its
- capabilities, we should try to adhere to those expectations: it should allow
- the user to type text anywhere in the paper body and it should allow for a
- title to be placed in the margin at the top.
-
- Additionally, good integration with the Workplace Shell is a necessity, so it
- should respond appropriately if the user drops a new font or color from the
- corresponding palette on the control.
-
- What should it look like?
-
- Similarly, it should look like a piece of loose leaf paper to minimize the
- "state shock" that a user might encounter when using the control.
-
- What capabilities will it have from a programming standpoint?
-
- This is often the most difficult to answer, although placing yourself in the
- role of a programmer using the control often helps. What capabilities would I
- want, were I to use this control? Personally, I would like to see the
- following "programmabilities":
-
- o The ability to query the contents of any line, as well as set the contents of
- any line.
-
- o The ability to query the text in the control, with each line ending with a
- "newline" character.
-
- o The ability to query what line the cursor is currently on as well as set the
- current line.
-
- o The ability to have the holes on the left or right side of the paper as well
- as have no holes at all. The ability to query where the holes are on the
- paper would be nice also, should I choose to implement a binder to contain
- the pages within.
-
- Notifications are also quite important, so it is a must to be notified
- whenever:
-
- o The cursor moves up or down
- o Any mouse button is clicked or double-clicked
- o The system-defined sequence for displaying the context menu is pressed
- o Help is requested
-
- Now we have a basis on which to design the control. Whether or not this list
- of requirements is complete or even doable remains to be seen and the list
- could be revised one or more times before it arrives at its final state.
-
-
- ΓòÉΓòÉΓòÉ 4.2.3. Design ΓòÉΓòÉΓòÉ
-
- It is often easiest to develop new code by repeatedly breaking the problem to
- be solved into smaller pieces until you arrive at a level where each piece can
- be coded and tested independently - or with little dependence - on the other
- pieces of the code. (C++ programmers will recognize this as being one of the
- inherent advantages of the language, since in the purist form of the language
- you are forced to do this as a preprocessing step.) In a related manner,
- writing a new window class is often easiest if you can break down the abstract
- view of the control into distinct components that are independent of each
- other.
-
- The paper control can be viewed as a collection of lines that can be written
- on, as well as a side margin and a top margin. Additionally, the holes (if
- any) are placed in the side margin. Since the user can type in only the top
- margin and/or the lines, the remainder of the control is reduced to a little
- more than proper painting of the components. Even so, the handling of the
- typing can be a major chore if we choose to process the keystrokes ourselves.
-
- Figure 1. Different parts of the paper control
-
- Fortunately, we don't have to. Borrowing the idea from the container control
- (my apologies to everyone if this was done previously in another control), we
- can create a single-line entryfield and by giving it in the proper position and
- size, it will behave exactly as though we processed each and every keystroke.
- But where will we store the handle to the entryfield? The answer lies in
- something that has existed since version 1.1 called...
-
- Window Words
-
- In C, there are provisions for declaring variables with 3 scopes of visibility:
- local, static, and global. Given the high possibility of having more than one
- window of a particular class existing simultaneously, the latter two are
- unsatisfactory for storing data since the data is not unique to a specific
- window; while local scope does provide for data unique to a window (since it is
- provided for on the stack of the thread running), it is also insufficient,
- since the data is not retained once the procedure exits. The PM developers saw
- these limitations and provided an answer in the form of window words.
-
- Window words are 1 or more bytes of storage that is reserved for all windows
- belonging to a specific class (called instances of that class). The actual
- number of bytes to be reserved is specified in the WinRegisterClass call and
- the window words can be set/queried using the functions WinSetWindowPtr and
- WinQueryWindowPtr. WinSetWindowULong and WinQueryWindowULong can also be used
- with the QWL_USER parameter. All of the standard PM controls reserve 8 bytes
- of window words; bytes 4-7 are used to store a pointer to a private data
- structure and bytes 0-3 are available for application use. We, too, will adopt
- this strategy; the memory will be allocated in the WM_CREATE message and will
- be released to the system in the WM_DESTROY message. The structure that this
- memory will be used for can be gleaned with a little thought.
-
- typedef struct _PAPERINSTDATA {
- USHORT usSzStruct;
- HWND hwndOwner;
- HAB habAnchor;
- HWND hwndText;
- PCHAR pchTitle;
- SHORT sMaxLines;
- SHORT sLine;
- CHAR aachLines[256][256];
- FONTMETRICS fmFont;
- LONG lForeClr;
- LONG lBackClr;
- } PAPERINSTDATA, *PPAPERINSTDATA;
-
- The fields in the structure are explained below:
-
- usSzStruct Contains the size of the structure. This is needed because
- PM performs a lot of thunking when calling its 16-bit code
- from a 32-bit application and it needs to know how large of
- an area to convert to a 16-bit segment.
-
- hwndOwner Contains the handle of the owner window for notification
- purposes.
-
- habAnchor Contains the anchor block of the window.
-
- hwndText Contains the handle of the entryfield window that will be
- used for input.
-
- pchTitle Points to the title text of the window, as set by the
- WinSetWindowText function (and on the call to
- WinCreateWindow).
-
- sMaxLines Contains the maximum number of lines that are displayed.
- The top line below the margin is the 0th line.
-
- sLine Contains the current line number.
-
- aachLines Contains the text of the control. By defining it in this
- fashion, we are defining a limitation of 256 lines and 255
- characters per line (plus 1 for the terminating `\0'.
-
- fmFont Contains information about the font currently being used.
-
- lForeClr Specifies the "foreground" color, which is used to draw the
- title text and that of the entryfield.
-
- lBackClr Specifies the "background" color, which is used to draw the
- text that has already been entered.
-
- Breaking Down the Walls
-
- As stated earlier, the implementation would be easier if we could divide the
- control's functionality into distinct parts and tackle each individually.
- Doing so yields the following:
-
- Painting This part draws the control on the screen, and can be
- considered the most important part from a user's
- standpoint, since first impressions are everything.
-
- User input This part handles the typing (including arrow keys, should
- we choose to process them) and mouse actions.
-
- Owner notifications This part handles the notification of the control's owner
- of significant events.
-
- Presentation parameters This part handles the presentation parameters, i.e.
- dynamically alterable characters of the control; foreground
- and background color and the desired font are the most
- widely used of these and can be modified using the "Color
- Palette" and "Font Palette" that are provided with the
- system.
-
-
- ΓòÉΓòÉΓòÉ 4.2.4. Additional Functions ΓòÉΓòÉΓòÉ
-
- Two important additional functions are the initialization and termination of
- the paper window class. The initialization function allows us to register the
- window class; the termination function will not be used in our implementation,
- although it should still exist to create a "balanced" function set. These two
- functions are the only two that should be exposed to the programmer.
-
- BOOL PprInitialize(HAB habAnchor)
- {
- WinRegisterClass(habAnchor,
- WC_PAPERPAGE,
- pprWndProc,
- CS_SIZEREDRAW|CS_CLIPSIBLINGS|CS_PARENTCLIP,
- sizeof(PVOID)*2);
- return TRUE;
- }
-
- BOOL PprTerminate(HAB habAnchor)
- {
- return TRUE;
- }
-
-
- ΓòÉΓòÉΓòÉ 4.2.5. Summary ΓòÉΓòÉΓòÉ
-
- We have seen how a little forethought can lead to a better design; by
- attempting to anticipate the needs of those who will use our control, we can
- shape the design of the control better and fill in more of the blanks that will
- become apparent once the control is actually used. Additionally, we have
- learned about window words and how they can be used to store data that is
- specific to a particular instance of a window class.
-
- Coming up in next month's article, we will wrap up the design of the control
- and begin the implementation of the window procedure.
-
-
- ΓòÉΓòÉΓòÉ 4.3. Programming the Container Control - Part 2 ΓòÉΓòÉΓòÉ
-
- Written by Larry Salomon, Jr.
-
-
- ΓòÉΓòÉΓòÉ 4.3.1. Where were we? ΓòÉΓòÉΓòÉ
-
- Last month I briefly introduced (in that oh-so-poor writing style that makes me
- unique) the container control and some of the programming basics that everyone
- should know. This month, we will continue this trek into the unknown by
- describing the tree view as well as some other nifty things. Also, we will
- write our first application using the container, on which we will build in next
- month's installment.
-
- From Another Viewpoint
-
- As a quick note, the name and text views were omitted last month because they
- are - from a programmer's perspective - identical to the icon view.
-
-
- ΓòÉΓòÉΓòÉ 4.3.2. The Wood Nymph's Delight ΓòÉΓòÉΓòÉ
-
- The tree view should be a highly familiar one; it parallels nicely with the
- directory structure of a hard disk and has been used in the File Manager (from
- OS/2 1.x) and the Help Manager as well as many other applications. It also has
- three subviews that can be specified in the CM_SETCNRINFO message: tree icon
- view, tree name view, and tree text view (these are specified by "or-ing" the
- CV_ICON, CV_NAME, or CV_TEXT flags with the CV_TREE flag in the flWindowAttr
- field, respectively). The difference between these views is in the way the
- data is represented: icon view displays an icon with the text to the right and
- to the left is a separate bitmap indicating whether the item is expanded or
- collapsed (if applicable); name view is the same as icon view except that the
- expanded/collapsed indicator is indicated in the icon; the text view provides
- only the text string with the expanded/collapsed indicator as a separate bitmap
- to the left.
-
- Note the difference between icon and name view. Remember when, in the
- beginning of the series, it was mentioned that there are a few differences
- between using the MINIRECORDCORE and RECORDCORE structure? This is one of
- those differences; since only the RECORDCORE structure has different fields for
- the expanded and collapsed icons, the name view cannot be used with the
- CCS_MINIRECORDCORE style.
-
- When inserting records into the container, the parent record is specified in
- the pRecordParent field of the RECORDINSERT structure. In the icon view, we
- specified this as NULL, since there is not parent/child relationships in that
- view. However, in the tree views, this is particularly important because it
- specifies how the records are to be displayed. All records that have children
- will be displayed by adding an expanded/collapsed indicator to its left. If,
- by some action of the user, you switch to any other non-tree view, all but the
- top-level records are hidden.
-
- Notifications
-
- In addition to the "normal" notifications, the container provides 2
- notifications specific to the tree view:
-
- CN_COLLAPSETREE This is sent to the owner after the container collapses a
- subtree. mpParm2 points to the record that was collapsed.
-
- CN_EXPANDTREE This is sent to the owner after the container expands a
- subtree. mpParm2 points to the record that was expanded.
-
-
- ΓòÉΓòÉΓòÉ 4.3.3. CNR2 - A Sample Application ΓòÉΓòÉΓòÉ
-
- Now let us delve into the depths of our first sample application; it not only
- showcases the container control, but it also contains some nifty tricks that I
- think you'll find useful. The structure is that of a "typical" PM application
- and it should not be new to you. In the code, I have placed several
- "landmarks"; these are marked by placing a comment of the form "@n" (where `n'
- is the landmark number) starting at column 55. These landmarks are used to
- point out things of interest and are discussed in more detail later.
-
- The application does nothing more than add a record for each month of each year
- for the number of years specified in NUM_YEARS. This is to show how the tree
- view is used. A popup menu is displayed whenever the system key/mouse sequence
- is pressed, allowing to you switch between icon and tree views, etc.
-
- Landmark 1
-
- This is simply to point out the typedefs used.
-
- typedef struct _CLIENTDATA { // @1
- USHORT usSzStruct; // Size of the structure
- HAB habAnchor; // Anchor block of the window
- HWND hwndFrame; // Frame of the client (== parent)
- HWND hwndCnr; // Container window
- HPOINTER hptrYear; // Icon for the year records
- HPOINTER hptrMonth; // Icon for the month records
- HWND hwndWndMenu; // Menu window
- } CLIENTDATA, *PCLIENTDATA;
-
- typedef struct _MYCNRREC {
- MINIRECORDCORE mrcCore; // Base structure
- CHAR achText[64]; // Icon text
- } MYCNRREC, *PMYCNRREC;
-
- //-------------------------------------------------------------------------
- // PFNSRCH is used by searchCnr(). The parameters are: container window
- // handle, record pointer, and user-data pointer, respectively.
- //-------------------------------------------------------------------------
- typedef BOOL (*PFNSRCH)(HWND,PVOID,PVOID);
-
- CLIENTDATA is the instance data for the client window. Granted, we could have
- used global variables, but that is poor programming practice so we avoid doing
- this (the only global we use is an constant array of pointers to the names of
- the months).
-
- MYCNRREC is the definition for the container records. Note that we are using
- the MINIRECORDCORE structure.
-
- PFNSRCH is a pointer to a function accepting three parameters and returning a
- boolean. It is used in a very interesting way that you'll see later.
-
- Landmark 2
-
- This is only to point out that while it is highly recommended that you allocate
- and insert as many records as possible each time sometimes it simply isn't
- possible. We have to allocate/insert each year separately followed by the
- twelve months.
-
- Landmark 3
-
- Since we specified FALSE in the fInvalidateRecord field of the RECORDINSERT
- structure, we have to send a CM_INVALIDATERECORD message to update the
- container.
-
- Landmark 4
-
- This entire procedure is useful, since (as described in a previous issue of the
- magazine) you cannot specify MIS_CONDITIONALCASCADE in a resource file. It
- should also be noted that since the cascade button takes up a bit more space,
- it is helpful to add a few blank spaces in the menu template to account for
- this. As a general rule, I use three blanks.
-
- Landmark 5
-
- Here is another useful procedure; it performs a recursive, post-traversal
- search of the container, calling a user-specified procedure at each record to
- see if it matches the criteria also specified by the caller. If you've ever
- wondered what a use for reserved parameter is, here is one. We use the
- reserved parameter to specify the record we are currently checking; by having
- the user specify NULL, we can check to see if this is the initial call.
-
- Of course, we could have eliminated the reserved parameter and had this
- procedure call another procedure with a reserved parameter to remove the burden
- from the caller, but that's too easy. (* grin *)
-
- Landmark 6
-
- This is to point out the allocation and initialization of the client instance
- data. Note that the cleanup is done in the WM_DESTROY processing.
-
- Landmark 7
-
- This is to show a bug in the container - if the user uses the mouse to invoke
- the popup menu, the container sends us a WM_CONTROL message. Not so if the user
- uses the keyboard, thus we duplicate the code and check for the WM_CONTEXTMENU
- message.
-
- Landmark 8
-
- Finally, we invoke the searchCnr() function specifying a pointer to a boolean
- that contains the desired select state. The searchSelect() function is an
- interesting one.
-
- BOOL searchSelect(HWND hwndCnr,PMYCNRREC pmcrRecord,PBOOL pbSelect)
- //-------------------------------------------------------------------------
- // This function is used to select/deselect all records. Note that it
- // always returns FALSE, so that searchCnr() will traverse the entire
- // record list.
- //
- // Input: hwndCnr - handle of the container window
- // pmcrRecord - pointer to the container record
- // pbSelect - pointer to a BOOL specifying whether to select the
- // record or not
- //-------------------------------------------------------------------------
- {
- WinSendMsg(hwndCnr,
- CM_SETRECORDEMPHASIS,
- MPFROMP(pmcrRecord),
- MPFROM2SHORT(*pbSelect,CRA_SELECTED));
- return FALSE;
- }
-
- See how it simply sets the record's select state and always returns FALSE -
- indicating that this record does not match - forcing searchCnr() to traverse
- the entire container.
-
- That's It!
-
- That is all there is to it! Note that while there is a lot of setup involved,
- the container is no more difficult to use than any of the other "standard"
- controls.
-
-
- ΓòÉΓòÉΓòÉ 4.3.4. Summary ΓòÉΓòÉΓòÉ
-
- We have seen how tree views can be used for hierarchical structures and how
- they are not much different that the icon, name, and text views described last
- month. We have used these concepts and introduced new ones in a sample
- application which will be used as a scaffold to add new features in future
- installments of this series.
-
- Next month we will describe the details view, selection states, direct editing,
- and possibly other ideas and will incorporate these concepts into our sample
- application.
-
-
- ΓòÉΓòÉΓòÉ 5. Columns ΓòÉΓòÉΓòÉ
-
- The following columns can be found in this issue:
-
- o Questions and Answers
-
-
- ΓòÉΓòÉΓòÉ 5.1. Questions and Answers ΓòÉΓòÉΓòÉ
-
- Welcome to this month's "Questions and Answers"! Each month, I collect various
- questions sent to me via email and try to answer each directly; the ones that I
- feel contribute the most to developers, whether in terms of information or as a
- nifty trick to tuck into your cap, get published in this column (being able to
- answer the questions also influences my decision *grin*).
-
- To submit a question, send mail to my email address - os2man@panix.com - and be
- sure to grant permission to publish your question (those that forget will not
- be considered for publication).
-
- This month's deluge of questions covered the following topics:
-
- o Dynamically adding and removing the min/max/size buttons?
- o Disabling hard error popups?
- o Online help for dialogs
- o Spawing a batch file
- o Deleting profile data
- o A Q about Notebook controls
-
-
- ΓòÉΓòÉΓòÉ 5.1.1. Dynamically adding and removing the min/max/size buttons? ΓòÉΓòÉΓòÉ
-
- David John Marotta (djm5g@virginia.edu) writes:
-
- I am trying to dynamically remove and add the min/max/size buttons and menu
- selections. I am having trouble doing this especially with the buttons. I
- can't determine which Win calls to use. Can someone help me? Thanks.
-
- David, what you need to do is create or destroy the appropriate windows with
- the id's corresponding to the frame controls you wish to add or delete and send
- the frame a WM_UPDATEFRAME message, specifying the FCF_ flags that have changed
- in mpParm1.
-
-
- ΓòÉΓòÉΓòÉ 5.1.2. Disabling hard error popups? ΓòÉΓòÉΓòÉ
-
- Christopher Fernandes (fernand@slinky.cs.nyu.edu) writes:
-
- I want to let users add files to a list using an add dialog. Since I could
- only find canned "open" and "save as" dialogs in the toolkit, I modified code
- from a freeware program (earlier version of OS/2 I believe) that works very
- well except that when the user selects a floppy drive that does not have a
- floppy in it, and I DosSetDefaultDrive to it I get that huge screen. Is there
- a call that is essentially a DosSetDefaultDrive that quietly returns an error
- code if there is no diskette?
-
- The solution is to use the DosError function, specifying FERR_DISABLEHARDERR |
- FERR_DISABLEEXCEPTION as its argument.
-
-
- ΓòÉΓòÉΓòÉ 5.1.3. Online help for dialogs ΓòÉΓòÉΓòÉ
-
- Christopher Fernandes (fernand@slinky.cs.nyu.edu) has another question:
-
- I don't have a main screen, just directly go into a dialog. I have a help push
- button that explains the program. The help text can be viewed using the
- VIEW.EXE program, so I know that I can spawn a "view file.inf" when the help
- push button is pushed. But I want to do it in a more standard way, using
- WinCreateHelpInstance and WinAssociateHelpInstance so that when the help push
- button is pushed in different child dialogs the user directly goes to the
- appropriate help topic. However, all the examples that I've seen use
- HELPTABLES. I don't want that since I don't even have a main window. Would it
- be possible to get some code that did this?
-
- Nowhere does it state that you have to have a "main" window in order to use
- HELPTABLE's. Since WinAssociateHelpInstance requires a frame window handle,
- with the following knowledge you can accomplish what you desire:
-
- o A dialog is a subclassed frame window
-
- o WinDlgBox is equivalent to the three calls WinLoadDlg, WinProcessDlg,
- WinDestroyWindow
-
- Thus, if you call those three calls instead of WinDlgBox, you can insert a call
- to WinAssociateHelpInstance after the call to WinLoadDlg, allowing you to use
- HELPTABLE's as you would in any other program.
-
-
- ΓòÉΓòÉΓòÉ 5.1.4. Spawing a batch file ΓòÉΓòÉΓòÉ
-
- Christopher Fernandes (fernand@slinky.cs.nyu.edu) has yet another question:
-
- My app generates a text file or optionally a Postscript file and spawns a
- lister/editor on these files if requested. I also want to allow printing of
- these files from within the program. I want to let the user specify a batch
- file, and then call that batch file with the file as the argument. However i'm
- getting an error of bad EXE type, so my question is could I have the line of
- code that spawns a batch file.
-
- Try spawning CMD.EXE with the arguments "/K batch_file arguments".
-
-
- ΓòÉΓòÉΓòÉ 5.1.5. Deleting profile data ΓòÉΓòÉΓòÉ
-
- Christopher Fernandes (fernand@slinky.cs.nyu.edu) can't stop himself:
-
- I save data across program invocations using the profile functions on a
- program-specific INI file. That works fine, but I can't find calls to delete
- data or to compact the file. Do the profile functions automatically compact if
- I write a zero-length object to that setting.
-
- If you call PrfWriteProfileData (or any of the other profile writing functions)
- specifying NULL for the pointer to the data, the data in the INI file is
- erased. Unfortunately, there is no explicit way to compact the file from a API
- viewpoint.
-
- As a side note, it is possible to create a new copy of an INI file that is
- compacted. You simply enumerate all of the applications and keys, query the
- data in the original INI file, and write the data to the new INI file.
- Unfortunately, you cannot copy the new version of the file over the original
- unless no application is currently using it - for OS2.INI and OS2SYS.INI, this
- is impossible unless you boot from your installation disks.
-
-
- ΓòÉΓòÉΓòÉ 5.1.6. A Q about Notebook controls ΓòÉΓòÉΓòÉ
-
- Gordon W. Zeglinski (zeglins@ccu.umanitoba.ca) writes:
-
- I've read through all of IBM's docs on Notebook controls and I cannot find out
- how to determine if the page is being turned to via a "tab" or the directional
- arrows.
-
- It would seem that there is a way to do this because Borland seems to do this
- in their C++ compiler for OS/2. The reason this is useful, is that it would
- make using "dummy" section seperator pages much easier to implement.
-
- I guess the question is how can one tell if the tab or arrow sections of a
- notebook control have brought the window to the top? Alternatively, How does
- Borland do it?
-
- One last question...
-
- I've noticed that double clicking the top tab, would occasionally cause the top
- page to go blank. I have set the control up to take care of all the redrawing
- automatically. Is this a know bug? or am I doing something wrong?
-
- There is no exposed way to determine if the directional arrows were used or
- not, but if you assume that the arrows always have the same id (a safe
- assumption in this case because there are always at most 1 set of these per
- notebook), then you can use WinWindowFromID to get the window handle of the
- arrows and then subclass them instead.
-
- In reference to your last question, I must say that I've never noticed this
- behavior in my applications that use notebooks, nor does the desktop suffer
- from this symptom, so it would appear that you have done something incorrectly.
-
-
- ΓòÉΓòÉΓòÉ 6. Future Attractions ΓòÉΓòÉΓòÉ
-
- In the next issue, we hope to return to your regularly scheduled programming,
- meaning a diverse set of authors presenting a broad set of topics. This is,
- after all, a programmer's magazine, and no one person knows everything about
- development for OS/2.
-
- Topics that we are interested in receiving (or even writing) articles on are
- "Online Documentation using IPF" and "Workplace Application Development". The
- former is obviously of greater interest to me since that will mean authors can
- no longer submit articles in ASCII flat files using the excuse that they don't
- know IPF. (*grin*)
-
- A plug is also needed for a new column, which we mentioned in the May/June
- issue. The column's objective is to review any one OS/2-development related
- "product" per month. The word "product" is used loosely, because freeware and
- shareware are also included in the scope of this column (and are preferred,
- since they are usually cheaper than their commercial counterparts). While it
- would be ideal to have a single columnist for this, we realize that there
- probably isn't anyone who has nothing better to do but review applications, so
- we consider this to be an open-author column. If you would like to review a
- product, keep reading.
-
- As always, we are always looking for (new) authors. If you have a topic about
- which you would like to write, send a brief description of the topic
- electronically to any of the editors, whose addresses are listed below, by the
- 15th of the month in which your article will appear. This alerts us that you
- will be sending an article so that we can plan the issue layout accordingly.
- Ideally, we will respond by sending you a copy of our Article Submission
- Guidelines (ideally, that is, because the guidelines are not completed yet.).
- The completed text of your article should be sent to us no later than the last
- day of the month; any articles received after that time may be pushed to the
- next issue.
-
- The editor's can be reached at the following email addresses:
-
- o Steve Luzynski - sal8@po.cwru.edu (Internet), 72677,2140 (Compuserve).
- o Larry Salomon - os2man@panix.com (Internet).
-
-
- ΓòÉΓòÉΓòÉ 7. Contributors to this issue ΓòÉΓòÉΓòÉ
-
- The following people contributed to this issue in one form or another (in
- alphabetical order):
-
- o Steve Luzynski
- o Larry Salomon, Jr.
- o Gordon Zeglinski
-
-
- ΓòÉΓòÉΓòÉ 7.1. Steve Luzynski ΓòÉΓòÉΓòÉ
-
- Steve Luzynski is the editor and creator of this magazine. He is currently a
- Computer Engineering student at Case Western Reserve University in Cleveland,
- Ohio, where he spends a lot of time being cold. Steve has released several
- small programs for IBM's TCP/IP using Pat Muellers rxSock library for REXX. He
- is currently working on a new mail program that he hopes to have finished
- before IBM beats him to the punch with TCP/IP 32bit.
-
- Steve can by reached via e-mail at sal8@po.cwru.edu or on Compuserve at
- 72677,2140.
-
- The old fashioned kind of mail can currently find Steve home for the summer at:
-
- Steve Luzynski
- 3604 S. 10th ST CT
- Blue Springs, MO 64015-6238
-
-
- ΓòÉΓòÉΓòÉ 7.2. Larry Salomon, Jr. ΓòÉΓòÉΓòÉ
-
- Larry Salomon wrote his first Presentation Manager application for OS/2 version
- 1.1 in 1989. Since that time, he has written numerous VIO and PM applications,
- including the Scramble applet included with OS/2 and the I-Brow/Magnify/Screen
- Capture trio included with the IBM Professional Developers Kit CD-ROM currently
- being distributed by IBM. Currently, he works for International Masters
- Publishers in Stamford, Connecticut and resides in Bellerose, New York with his
- wife Lisa.
-
- Larry can be reached electronically via the Internet at os2man@panix.com.
-
-
- ΓòÉΓòÉΓòÉ 7.3. Gordon Zeglinski ΓòÉΓòÉΓòÉ
-
- Gordon W. Zeglinski can be reached at zeglins@cc.umanitoba.ca (formerly
- zeglins@ccu.umanitoba.ca).