WW WW WW PPPPPPPP JJ WW WW WW PP PP JJ WW WWWW WW PP PP JJ WW WW WW WW PPPPPPPP JJ WW WW WW WW PP JJ JJ WWWW WWWW PP JJ JJ WW WW PP JJJJJ ---------------------------------------------------------------- The Windows Programmer's Journal Volume 01 Copyright 1993 by Peter J. Davis Number 04 and Mike Wallace Apr 93 ---------------------------------------------------------------- A monthly forum for novice-advanced programmers to share ideas and concepts about programming in the Windows (tm) environment. Each issue is uploaded to the info systems listed below on the first of the month, but made available at the convenience of the sysops, so allow for a couple of days. You can get in touch with the editors via Internet or Bitnet at: HJ647C at GWUVM.BITNET or HJ647C at GWUVM.GWU.EDU (Pete) CompuServe: 71141,2071 (Mike) 71644,3570 (Pete) Delphi: PeteDavis GEnie: P.DAVIS5 or you can send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Dr. Fairfax, Va. 22032 We can also be reached by phone at: (703) 503-3165. The WPJ BBS can be reached at: (703) 503-3021. The WPJ BBS is currently 2400 Baud (8N1). We'll be going to 14,400 in the near future, we hope. LEGAL STUFF - Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups, Windows for Pen Computing, Win32, and Win32S are registered trademarks of Microsoft Corporation. - Turbo Pascal for Windows, Turbo C++ for Windows, and Borland C++ for Windows are registered trademarks of Borland International. - WordPerfect is a registered trademark of WordPerfect Corporation. - Other trademarks mentioned herein are the property of their respective owners. - WPJ is available from the WINSDK, WINADV and MSWIN32 forums on CompuServe, and the IBMPC, WINDOWS and BORLAND forums on GEnie. It is also available on America Online in the Programming library. On Internet, it's available on WSMR-SIMTEL20.ARMY.MIL and FTP.CICA.INDIANA.EDU. We upload it by the 1st of each month and it is usually available by the 3rd or 4th, depending on when the sysops receive it. - The Windows Programmer's Journal takes no responsibility for the content of the text within this document. All text is the property and responsibility of the individual authors. The Windows Programmer's Journal is solely a vehicle for allowing articles to be collected and distributed in a common and easy to share form. - No part of the Windows Programmer's Journal may be re-published or duplicated in part or whole, except in the complete and unmodified form of the Windows Programmer's Journal, without the express written permission of each individual author. The Windows Programmer's Journal may not be sold for profit without the express written permission of the Publishers, Peter Davis and Michael Wallace, and only then after they have obtained permission from the individual authors. Table of Contents Subject Page Author(s) ----------------------------------------------------------------- WPJ.INI ....................................... 4 Pete Davis Letters ....................................... 6 Readers Midlife Crisis: Windows at 32 ................. 9 Pete Davis Beginner's Column ............................. 14 Dave Campbell Owner-Drawn List Boxes ........................ 25 Mike Wallace Beginner's Column for Turbo Pascal for Windows 30 Bill Lenson Hacker's Gash ................................. 31 Readers Microsoft's Windows Strategy .................. 34 Pete Davis Accessing Global Variables Across DLL ......... 38 Rod Haxton Getting A Piece Of The Future ................. 41 Peter Kropf The "ClickBar" Application .................... 44 WynApse Getting in Touch with Us ..................... 45 Pete & Mike Last Page .................................... 46 Mike Wallace (If you're wondering where the C++ Beginner's column is, it's because we haven't heard from the column's author. We hope to have it next month. See WPJ.INI for further explanation.) Windows Programmer's Journal Staff: Publishers ......................... Pete Davis and Mike Wallace Editor-in-Chief .................... Pete Davis Managing Editor .................... Mike Wallace Contributing Editor ................ Dave Campbell Contributing Editor ................ Bill Lenson Contributing Writer ................ Rod Haxton Contributing Writer ................ Peter Kropf WPJ.INI by Pete Davis Crazy, crazy, crazy. That's the only way to describe how things have been lately. Got some good news, some bad news and a bit in-between. First of all, I'll pour out some of the bad news. We've been unable to get in touch with the author of the C++ beginners column. This happens sometimes with a magazine like this and it could be for any number of reasons. We will continue to try to get in touch with him and hopefully have an article for next months issue. The second piece of not-so-good news is about me and you may think of it as good news, depending on your opinion of me. I probably won't be able to do quite as much writing for the magazine for the next 7-10 months. I will get in at least one article (for the Win 32 column) each month, but other than that, I won't be able to do much else. For one thing, I'm going to have to cancel the Install program. I'll discuss some points for the Install program in a sec. I can't say yet why I won't be able to write as much but hopefully, by the next issue, I will be able to tell you what it is. Of course, things might not work out, and I'll be writing as much as ever for the next 7-10 months. Well, I guess that was cryptic enough. I'll discuss it next month. As for the install program, it looks like I won't be able to continue it. I've looked into the question of installing DLLs while the something is using them. It seems that Windows'install program puts the files in a temporary directory and copies them in before a restart or shutdown of Windows. This is what I have been told by several people, including someone from Microsoft. I have been through Microsoft's Install code in the past and I don't remember seeing anything about that, but I wasn't really looking in the right places, in retrospect. I will post this code on the WPJ BBS if you're interested. I would love for someone to pick up the install program and finish it off for me. If anyone's interested, please, get in touch with us. Let's try to get into some good news now. The good news is that we're going to be covering Win32 A LOT! This doesn't just mean Windows NT, but also Win32s and, eventually, Win32c (won't be released for another year or so). This month I'm starting off with some NT and Win32c specific stuff. Later I'll going into using the Win32s API to write 32-bit apps for Windows 3.1. There's really a lot of promise in the 32-bit application arena and we intend on showing you all about it. We've also got Peter Kropf who will also be doing a Win32 column. Most of his is going to be discussing Windows NT so after this month, I'll try to leave that to him and I'll move on to Win32s and some less NT-specific stuff. We've got Bill Lenson who will be doing a new column on Turbo Pascal for Windows for beginners. We've all been waiting for this, so we're glad - 4 - to have him aboard. Rod Haxton is back with a really interesting way to handle Global variables in Windows DLLs. As with all these sort of under-the-covers weird things, it's not a recommended, it's just a method. DLLs shouldn't use Global variables from the main program, but sometimes you don't have much of a choice, as Rod will explain. Well, that's about it for me, just to say that next month I hope to have some really good news for all of you. Peace. Pete Davis - 5 - Letters Date: 21-Mar-93 22:07 EST From: Eric Fildebrandt [76326,1470] Subj: WPJ Format I have read your past three issues and can't thank you enough. I am venturing into the Windows programming environment and need all the assistance I can get. As to the format, I really like the WinHelp format. It is easily navigated and can be placed in a Program Group in Windows. This way it can be used as a reference when programming. I wonder if you could discuss the best way to change the background color of a window since everyone else simply used the default white background using GetStockObject. I have tried using CreateSolidBrush and it works basically, however, after I close the window, the chosen background color remains as the background color for the icons in the Program Manager window until that window is somehow refreshed. I am also interested in knowing how to drag and place bitmaps in a window. I am also really confused on how to use the WM_PAINT message to send output to the window, especially when that output changes during the run-time of the program. Let's take the windows solitaire game as an example. How would the WM_PAINT message be used to change the display as each card is played, or is it? Thank you for providing a forum for those of us trying to get acquainted with Windows. Eric Fildebrandt Electrical Engineer/Computer Programmer Editor [P.D.]: Eric, let me see what I can do for you here. I messed around with some code that was supposed to be the basis for a solitaire game that I was going to write. The world conspired against me and limited a day to 24 hours, so I didn't have time to finish it, but here's what I learned. My first attempt was to just draw the bitmaps in their locations. When a mouse button was pressed, I would check the mouse position. If it was over a card, I would pick it up and the move it by BitBlt-ing it with the XOR operator so that you could draw it over itself to erase it. This is used in moving. You have two positions, old and new. You draw over the old (with XOR) and draw in the new position (also with XOR) and your card just moved. THIS IS NOT THE RECOMMENDED WAY. This was a disaster and it was a lot slower than it had to be. If I were to go back and do this, my first move would be to place the card bitmaps inside of static objects. You could keep track of what the mouse is currently above by checking the WM_SETCURSOR message and storing the result in a static variable. Then when a WM_LBUTTONDOWN message comes, check to see what the cursor was above. For a sample of using the WM_SETCURSOR, see WPJV1N3 Hackers Gash, topic #2. I hope this helps. If anyone else has some ideas, please send them in. - 6 - Date: 24-Mar-93 16:15 EST From: Francois Bruneau [100113,1156] Subj: Adapt the old Memory functions of Turbo C++ I have some code that works with Turbo C++. I don't know how adapt the memory functions "memmove","memset","realloc" to the Windows 3.1 Borland C++ . For example I have code about matrix class: class matrix{ protected: int lgn,col; double* coeff; void init(int l,int c,double* cf=0); public: matrix(int l,int c,double* cf) { init(l,c,cf); } } void matrix::init(int l,int c,double* cf) { lgn=col=0; coeff=0; coeff=new double[l*c]; lgn=l; col=c; if (cf) memmove(coeff,cf,l*c*sizeof(double)); else memset(coeff,0,l*c*sizeof(double)); } Elsewhere: coeff=(double*) realloc(coeff,lgn*col*sizeof(double)); I have no idea for the translation to GlobalLock,GlobalFree... Can you help me? Francois Bruneau. Editor [P.D.]: Well, Francois, I'm not sure if I understand your question exactly, but I'll we what I can do. First of all, for allocating the memory, you're better of using Local memory if you can, but if you're data is large, use Global. You'll need to lock the data before you can manipulate it. That means before doing a memmove or memset, use the Local/GlobalLock function (This assumes you've done your Local/Global Alloc with LMEM/GMEM_MOVEABLE!) Next, if you're using LocalAlloc, then you can use memmove and memset or their equivalent functions which are available in all compilers. If you are using GlobalAlloc, you need to use functions that will work on FAR pointers. If these functions aren't available in your compiler, you'll have to write your own. Also, be aware the Local/GlobalAlloc will return a handle, not an address. To get the address, you need to do a Local/GlobalLock. - 7 - Date: 31-Mar-93 00:52 EST From: James F. Stoves [72561,746] Subj: Replacing DLL's Chris, I was just reading your letter in WPJ regarding the replacement of DLLs. I just installed EXCEL and WORD on a PC at work, and in thinking about it, the Install Program took over the machine. What I mean is that "MICROSOFT" logo appeared on the top, miscellaneous propoganda in the middle, and a progress bar on the bottom. The window had no "system menu" or minimize/maximize block. There was a cancel pushbutton, but ostensibly that would have shut down the entire install process. My clock (which is set to "always on top") did not appear. I did try "Ctrl+ESCape" once during the install process and it did not work. Yet the "setup" program is launched from within Windows, so it must be there somewhere... I was wondering if the way Microsoft gets around the "used DLL" problem is by making sure that all DLL's are unused by booting them out. In other words, causing everything to somehow be magically unloaded while the install is running, replacing the DLLs, then a reload when the install process ends. Just a thought, probably of minimal value, but presented for your amusement..... Jim Editor [P.D.]: Jim, I mentioned in WPJ.INI that I've seen the code for the install program. I don't remember this part either, but I can think of a way it could be done. First of all, to get rid of everything else on the screen and get a bitmap on the screen, all you need is a dialog box with no SYSMENU, no CAPTION bar and no border to be sized for the entire screen. Next, draw your bitmap on top of it. Now, why isn't your clock showing up? Well, for your clock to be on top, it has to get a message letting it know it was covered up so it can redraw itself on the top. Since Windows is a non-preemptive multi-tasking system, all the Install program has to do is not release control until it's done. You are taught from day one to keep your programs nice and have them yield time on a regular basis. If you don't yield the queue, nothing else will run (except the kernel and any calls you make). So, your clock would stay hidden. That's how I would do it, and I wouldn't be suprised if that's how the install program handles it. If anyone else has comments on this, I'd love to hear them. - 8 - Midlife Crisis: Windows at 32 By Pete Davis So, what's this new column all about? Well, Win32 obviously, and yes, this column will get into the nitty gritty of Win32 programming. I'm not sure how much appeal this will have because I don't know how many people out there actually have it. Mike and I have had it for a couple months now and I just recently got the Win32 API Programmer's Reference (Pre-Release). This wallet-buster collection of a whole two books (at a mere $90) has almost everything you need, NOT. Ok, well, as you saw in the last issue, Microsoft's not known for their amazing book deals, but hey, when the choices are Microsoft's API reference or Microsoft's API reference, I tend to take the former, or was that the latter? Oh well, I think you get the picture. Second question: A whole column for Win32? Well, yes. Even Microsoft thinks Windows NT will only take a small percentage (10-20 percent) of the Windows 3.1 market. But, as the title implies, this isn't just a Windows NT column, this is a Win32 column. Win32 includes the Win32s API which allows Windows 3.1 to run 32-bit applications. This has a lot of promise, and we'll be talking in detail about all of this. Actually, there are going to be two Win32 columns. The second one is going to be more NT specific. Ok, let's get a little terminology covered first. You've got your Windows 3.1, Windows NT, Win32 and Win32s. Windows 3.1, well, if you don't know what it is, maybe you're reading this magazine by accident. Windows NT, of course, is Microsoft's Windows New Technology (NoT, whatever). Windows NT is a 32 bit operating system. It's more than just a faster Windows, though. It's a huge departure from Windows 3.x. Windows NT is its own operating system and runs without MS-DOS underneath (although an MS-DOS clone runs underneath Windows NT). Windows NT, unlike Windows 3.x, is a pre-emptive multi-tasking operating system. This means that while in Windows 3.x, if a program locks up, so does Windows, while, under Windows NT, if an application locks up, it doesn't affect the rest of the system. The reason is that in Windows 3.x, the multi-tasking is based on the message queue and when the message queue stops, so does Windows. (There is one exception: MS-DOS sessions under Windows 3.x are pre-emptively multi- tasked). Windows NT handles everything based on time-slicing instead of the message queue. This means that each application gets a short period of time to run, then the next application gets a bit of time, and then the next, and so on, until the first one gets to go again. This is what pre-emptive multi-tasking is all about. Oops, where were we, oh yeah, the last two: Win32 and Win32s. These are both about the same thing. Both are the 32-bit APIs for programming Windows. Win32 is the 32-bit API that you use to program under Windows NT. The Win32s is the same thing, except that it allows your 32-bit applications to run under Windows 3.x through the use of some trickery of VxDs (32 bit device drivers) and some DLLs. The Win32s API is actually a subset of the Win32 API. It doesn't have threading, security, and some - 9 - other things. Actually, the functions are in the API, but since they can't be supported under Windows 3.1, they simply return a failure. This means that you can program in the features for NT, but if it's being run under Windows 3.1, certain features just won't work. So, Windows 3.x is covered with the rest of the magazine. The other three, Windows NT, Win32 and Win32s are covered by this column and the NT column. So, that's it for the little intro on terminology. Win32 is big, and when I say big, I mean REAL BIG. It's about twice as complex, programming-wise, as Windows 3.x. Now, that might not be exactly right, there are some things that are made easier, but there are so many new features, that it can be very overwhelming. I'm not going to write a general overview of Win32 programming because I don't have 6 months to do that. The best way to handle this whole thing, I guess, is to take it a bit at a time, so each month I'll pick a topic to cover and after about 600 issues, we'll start to get comfortable with the Win32 API. Before we get too deep into this (and believe me, we're going to get in over my head, a bit), I want to make one thing very clear. I am not a Win32 expert. I've been going by what is quite possibly out-of-date documentation. I don't have the $360 for Windows NT Manuals. Microsoft says to use the online documentation, to which I say, 'Yeah, you try going through a 3 meg help file on a CD-ROM drive with 800ms access time!'. I am going to try to get the rest of the documentation as soon as I can. I also have Helen Custer's 'Inside Windows NT' which is a great book, but it isn't exactly specific on the programming aspects. It does, however, provide an excellent discussion on how NT works in a general sense. Today we're going to talk about Processes and Threads and Synchronization, and all that fun stuff. Damn, that means more terminology, doesn't it? Well, this will be short. Processes, in terms of Windows NT, are programs. Each program is a process. Within a single process you can have multiple threads. Threads are basically just procedures. The only difference is that you can have multiple threads execute simultaneously. This is a really nice feature if you have multiple processors in your machine, because with Windows NT, you can have different threads running on different processors, which makes for some really nice multi-tasking. Synchronization is something that comes up when you have multiple threads running at once. Synchronization keeps things from getting out of control. Since synchronization is such an integral part of concurrent thread programming, we'll be talking about it quite a bit here. I'm picking threads and syncronization as a starting point for several reasons. It's one of the most important, yet difficult, obstacles to overcome in programming for the higher end Win32 systems (Windows NT and the yet-to-come 'Chicago'). Understanding how threads and syncronization work will give you a good understanding of how many parts of NT work and interact which is essential to programming for it. In later articles I will move to less NT-specific subjects, however. Let's look at an example: - 10 - Let's say you have a database program. As a feature of your database, a user can go print a report, and even if it's not done spooling your report, it lets you go on to work with the database. So, you go select your print report option and then decide to make some changes to some of the records in the file. Well, what happens if you update a record in the file at the same time that the reporting thread is reading that record? Well, that's a problem (and admittedly, not the most realistic, but bear with me). Synchronization allows your update thread to keep all other processes from touching that record until the record is updated. It's kind of like a database record lock, which is usually handled by the operating system (hence the lack of realism), but it's the idea that I'm trying to get across. Here's another example, you have a a program that does a bunch of repetetive calculations. To take advantage of your muliple CPUs (getting realistic now, aren't I.) you decide to break it up into threads that run concurrently to speed things up. Well, you could send off a thousand threads at once, but sooner or later you're going to run out of memory, performance will go through the floor, etc. So, you need to keep the number of threads limited. First, going to our last example of the calculations, that kind of synchronization requires a Semaphore. A Semaphore keeps a count of how many threads are currently using a resource (in this case, the CPU). When a certain number of threads, as determined by the programmer, are using this reousource, no other threads can get to it until one of the ones currently using it ends, or gives up their lock. Theres also the event object which is set when a particular event takes place. This will release all threads waiting for the event when it's set to the signalled state. When you've got a few applications that want to use a single resource, say a printer, you need to use a different type of synchronization called a mutex. A mutex is a kind of synchronization, like Semaphores in a way, except only one thing can use the resource at a time. Since we've just discussed Mutex, let's go on to Critical Sections. Critical Sections are a lot like Mutex objects. Critical Sections work with blocking a single resource at a time. The Critical Section, however, is different from the other three objects we've talked about in a few very important ways. First of all, Critical Sections only work within a single process (also known as a program, damn I love these fancy words). The other synchronization objects will work within a single process, but they'll also work across all processes. If you want to use mutual exclusion in a single process and not globally, go with the Critical Section. If for no other reason, it's faster, and we're all speed mongers, aren't we? I'd like to back up just a bit, for a second. I've been saying a lot of stuff like, 'multiple threads is great if you have multiple processors', and you're probably thinking, well, I don't have multiple processors, so why go through all the trouble of threading if it's not going to speed - 11 - things up? Well, there are other reasons for threading and that's what I want to discuss for a second. In a multiple processor enironment, the better the threading, the more the operating system can utilize all the processors, so it's obviously a good idea there, I think we've established that. In a single processor environment, however, situations can arise where threading can still speed up applications. Let's say you have a program that has to read in some data from a file and then create a report from that data. Let's also say that before the report can be generated, some initialization steps need to occur. Well, disk usage can really slow down a program. Why not send off a thread to read in the file, and then go on to do the initialization while it's reading the file and then both of you meet back and do the report? Kind of a 'you take the high road and I'll take the low road' idea. This way, the main program's not bogged down by the speed of the hard drive quite as much. Threads are also excellent candidates for server software in a client-server system. Each thread could, say, handle a client. I joked earlier about how we all have multi-processor machines, right? Well, actually, don't be too surprised if they start showing up at reasonable prices over the next few years. Don't laugh, look how much a 386 runs these days. You'd probably have laughed 2 years ago if I told you the prices would get that low. Don't be to surprised if you see add-on cards, at reasonable prices, that carry, say, 3 additional 486s as co-processors for your 486, or 5 486s as co-processors, or whatever. The point being, it could happen, and I'm betting that it will, especially now that there's an operating system that can handle it. So, on to the next thing, thread priorities. There are five levels of thread priority that you can assign. Now, in Unix, the way that this works, if I recall correctly (if you ask anyone, they'll tell you my recollection is as reliable as bathroom graffiti) Unix will accept a thread priority, but modifies it based on the kind of things the thread does, like disk I/O lowers a thread priority, and the longer a thread runs, the lower it's priority gets. Something like that. Anyway, NT lets you set your thread priority at one of the 5 levels. The highest priority threads go first. As each thread in the higher levels completes, threads in the lower levels are run at each level one at a time, like the first. All of this is calculated against the process' (not the thread, but process) base priority, which is assigned by the operating system. This is modified by various factors, such as a process running in the foreground has it's base priority raised, whereas a process in the background has it's priority lowered. Also, to make sure all threads get to run, NT will temporarily raise the priorities of threads that have been waiting for a long time. It's all pretty complex, but the idea of assigning priorities is just to give the operating system an idea of how important you think a particular thread is. The best way to think of it is this. Take the processes' (not the thread, again) base priority. Your thread can be assigned a priority of -2 to +2 against the processes' base priority. i.e. the lowest thread priority is the base thread priority-2. This is the lowest priority your thread will ever see. If, however, your thread's been sitting around for a while waiting to run, NT will give it a little boost to make sure it runs. - 12 - I keep looking over this and I see a million ways to improve this article, but given the time I can't do it this month. I know it's a lot to absorb and it's really just meant to give you a taste. Next month I'll have some code that actually uses threads and I'll explain how it works. I'm also going to start talking about Win32s and give you some code that you can run on your Windows 3.1 system with the Win32s DLLs. To avoid adding the space of the API to the magazine, for those that aren't interested, I'll have the code available on the BBS along with the necessary Win32s DLLs and VxDs. Don't let this months column scare you off. I probably could have explained things a little better, with a little more time. Things should become more clear when you see some code. I suggest that if you have the means you do get a copy of the Win32s API and DLLs. The future of Windows is going to be 32-bits. I hate to sound like a Microsoft Rep, but they're right. 32-bits is the next logical step and everyone's going to have to make it eventually. Better to hop on now and get a jump on the competition. I'm sure I'll get lots of questions on this article to clarify just about everything I've written so sloppily here. That's fine, send me the mail and I'll be sure to respond, if not personally, as part of the magazine. The real problem was I bit off a bit more than I can chew this month and I'll try not to do that again. See ya and don't forget to write. - 13 - Beginner's Column By Dave Campbell Remember last month I made some silly comment about getting buried in work? I must be a prophet!! Anyway, here we are again, and with any kind of luck next month you'll also find a review of Microsoft Visual C++ by me. I have been using it since January, and am convinced it is a great way to go. This month we are going to add an "ego" box with a live compile date in it to the hello application, and talk some about menus. Ego Box I like to call the "About" box an Ego box, because when was the last time you saw someone (me included) that put something other than 'Look who wrote this' into an 'About' box? For that reason, we HAVE to do an Ego box, or what would people think? Thanks to Jeffrey M. Richter in his great book "Windows 3: A Developer's Guide", we can add a little bit of usefulness to the Ego box by showing a live compile date. I find this very useful for configuration management, when I have two (or more) copies of the same thing floating around for testing. The dialog box declaration looks very much like the setup dialog box from last month: ABOUT DIALOG 37, 10, 144, 92 STYLE WS_POPUP | WS_DLGFRAME FONT 8, "Helv" BEGIN CONTROL "Copyright (C) 1993 WynApse", -1, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 8, 130, 11 CONTROL "CompuServe: 72251,445", -1, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP, 17, 17, 109, 8 CONTROL "Version Timestamp:", -1, "STATIC", WS_CHILD | WS_VISIBLE, 36, 35, 71, 12 CONTROL "", ID_VERSION, "STATIC", WS_CHILD | WS_VISIBLE, 22, 45, 100, 9 CONTROL "OK", IDOK, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 54, 63, 36, 24 END Starting at the top, the STYLE line is different from the setup box. Instead of WS_CAPTION, we have WS_DLGFRAME. For Ego boxes, I don't generally put a caption. It doesn't serve a lot of purpose, other than being able to move the box around, and how often do you do more than click OK on an About box? This is personal preference, you understand. The - 14 - WS_DLGFRAME puts a nice frame around the box, and serves no purpose other than aesthetics also. The top three lines of the dialog are essentially identical in their properties: CONTROL "text", ID, "class", style, x-position, y-position, x-width, y-width This is the form of the generalized control statement. The first three lines have an ID of -1. This is because they are static controls, and do not send messages back to the parent window. There are six control classes: button static edit scrollbar listbox combobox At this time, we are only dealing with two of them: button and static. The version number will be written to the dialog box, but that is done by the WinApp, not by the user, so it isn't an edit box. The style parameter can consist of logical OR chains of individual style declarations, of which there are a multitude. I am going to just talk about them as we get to them. The first control line contains: SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP where SS_CENTER : Center the text in the box WS_CHILD : child window WS_VISIBLE : control is initially visible WS_GROUP : first item in a group [we'll deal with this later] The fourth control contains: BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP where BS_DEFPUSHBUTTON : Default pushbutton, the one selected upon startup WS_TABSTOP : goes along with the group declaration The fourth control is different in that it has "" for the displayed text, and has a defined control ID, ID_VERSION. ID_VERSION is defined in - 15 - hello.h, and is used in hello.c to place the text properly. HELLO.DEF has a slight change: DON'T FORGET TO PUT THE DIALOG BOX PROCEDURE IN THE DEF FILE!! This means the exports list of the def file becomes: EXPORTS WndProc @1 HelloDlgProc @2 AboutDlgProc @3 Now we're ready to discuss displaying the box. This takes place identically to the invocation of the setup box from last month. A menu choice is made, and a message is sent to our main Windows Procedure, telling us the user wants to see a dialog box. This time, we have added a message IDM_HELPABOUT to hello.h to handle this. The function prototype BOOL FAR PASCAL AboutDlgProc(HWND, WORD, WORD, LONG); is listed with the other prototypes above the main body of code. "About" is added to the system menu, which will be explained later in this article. A static FARPROC for the pointer to the dialog procedure is declared early in WndProc, inline with the one to the setup box. Then, just below the case statement for IDM_SETUP, is one for IDM_HELPABOUT. The two case statements match with the exception of the name of the dialog template to use from hello.dlg. The About box code is: case IDM_HELPABOUT : lpfnAboutDlgProc = MakeProcInstance(AboutDlgProc, hInst); DialogBox(hInst, "About", hWnd, lpfnAboutDlgProc); FreeProcInstance(lpfnAboutDlgProc); return 0; We must get a long pointer to a function, lpfnAboutDlgProc, to use in the DialogBox function call. This is actually playing a game with memory segments and far pointers, and segment reloading to enable Windows to find our dialog box function in memory. All the segmentation manipulation that Windows gives you for free is through small pieces of memory called "thunks", or instance thunks, or reload thunks which binds a data segment to the address of a function. MakeProcInstance takes the procedure in the first parameter, and binds it to the data segment of the application whose handle is in the second parameter. The return value from MakeProcInstance is actually an address of a reload thunk. Aren't you glad you know this? Now you do....... .....now you've forgotten it, and can get on with programming Windows. Since you know this, you'll realize how important it is to release that "thunk" when you're finished with it by executing the FreeProcInstance call. In DOS, we could be fairly certain that once our program exited, things would get cleaned up. That wasn't good programming practice then either, but it worked. If we make that kind of error in Windows, we leave all manner of little potholes in memory, and very often run out. - 16 - The parameter "About" in the call is the title of the dialog box we built in Hello.DLG. Because of the 'DialogBox' call, this dialog box will be modal, and control will not return to the main window until OK is pressed in the dialog box. We do a return 0 here since we have handled the message, and have no need to execute the default operation. That gets us to the dialog box procedure itself, 'AboutDlgProc': BOOL FAR PASCAL AboutDlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam) { char szBuffer[100]; switch (message) { case WM_INITDIALOG : wsprintf(szBuffer, "%s at %s", (LPSTR) __DATE__, (LPSTR) __TIME__); SetWindowText(GetDlgItem(hDlg, ID_VERSION), szBuffer); return TRUE; case WM_COMMAND : switch (wParam) { case IDOK : EndDialog(hDlg, wParam); return TRUE; default : return TRUE; } default : return FALSE; } } /* AboutDlgProc */ The WM_COMMAND switch case is virtually identical to the setup box from last time, the new stuff being the WM_INITDIALOG case. The WM_INITDIALOG message is the first message a dialog box receives. At that point in time, we fill in a message buffer with compile date and time in this manner: wsprintf(szBuffer, "%s at %s", (LPSTR) __DATE__, (LPSTR) __TIME__); __DATE__ and __TIME__ are predefined global identifiers that contain the date and time that processing began on the current file. This line of text is inserted into the ID_VERSION control on the face of the dialog by the following line: - 17 - SetWindowText(GetDlgItem(hDlg, ID_VERSION), szBuffer); GetDlgItem returns the child window's handle for the control, and allows us to insert our text into the dialog box. Menus Now let's discuss menus. Because everyone is familiar with them, we should be able to beat them to death pretty soundly. How many menus are there in Windows and how do we enable/disable them? Anybody? Ok, I'll address it: There is a system menu which may be part of any window or dialog box (which is just a child window). Then there is a menu that may be declared in the window class declaration. There are popup menus that can be used pretty much anywhere. System Menu The system menu is enabled/disabled with the properties of the window declaration. This is invoked by clicking the dashed line boxed in on the left of the caption bar (the System Menu button). This is also the menu you get when you single click an icon. The system menu is enabled by including the WS_SYSMENU style in the window creation. Application Menu The Application menu is declared at class-registration time of the window: wc.lpszMenuName = szAppName; Normally, the name of the program is also the name of the menu, so szAppName is useable here. The ninth parameter in the CreateWindow call is of type HMENU, and this overrides the menu in the menu class. This gives you the option of having multiple windows use the same class, and have different menus. If the one in the class is the one you want (as we are doing) leave the ninth parameter as NULL. Popup Menus Popup menus are declared either as part of the main menu, or possibly on the fly during run-time, and may displayed as an action from the main menu, or possibly a run-time event. - 18 - CUA As with any user interface, and more importantly, an interface that is shared, you want to provide your users (it might be you) an easily recognizeable way to use your product. It may be cute to put the help pull down on the left side of the main menu, and it certainly would grab people's attention, but not in the way most of us would like! All of this melts down into me stating that in the case of menus, if you want one, make it look like everyone elses. At least for the major pieces. This is called CUA, which stands for Common User Access. The orignial standard of CUA was developed by IBM. The current standard is Microsoft's "Application Design Guide" (ISBN 1-55615-384-8). The most important point being to be standard! The hello.rc code for the menu is quite long, and only pieces of it will be show here: Hello menu begin popup "&File" begin menuitem "&New", IDM_FILENEW menuitem "&Open...", IDM_FILEOPEN . end popup "&Edit" begin menuitem "&Undo\tCtrl+Z", IDM_EDITUNDO menuitem "&Repeat", IDM_EDITREPEAT . end popup "&Sample" begin menuitem "&Dialog", IDM_SAMPLEDLG menuitem separator menuitem "&Alternate..." IDM_ALTERNATE end popup "&Help" begin menuitem "&Index", IDM_HELPINDX menuitem "&Keyboard", IDM_HELPKBD . menuitem "&About Hello", IDM_HELPABOUT end end The menu has a basic structure: - 19 - "Name" menu begin end inside the begin..end pair is the menu itself. Each entry in our menu is listed as 'popup'. This means that each of the entries is a kick-off point for a popup, or pull-down type of menu whose structure follows. The structure for the popup menus is identical to that of the main menu with the exception of the word 'popup': popup "Name" begin menuitem "Sub1", IDM_SUB1NAME menuitem "Sub2", IDM_SUB2NAME . end Popups can have popups can have...you get the idea. Each menu item has three parts: 1) The text that appears on the menu 2) The ID number of the menu item 3) An attribute for that item Menu Text The menu text is copied directly to the menu in the manner you type it, with the addition that any character preceeded by an ampersand, "&", is underlined in the menu. The underlined characters, when combined with the Alt key, provide the user a 'hot-key' method to that item. Without the "&" the user may still use the Alt key with the first letter of any menu item. Two control characters '\t' and '\a' are optionally used inside the text. The tab character, '\t' will space the menu item in a new column far enough to the right to make room for the longest text line in an accompanying popup menu. The '\a' is normally used with HELP, and will right-justify the menu selection. Menu ID The menu IDs are defined as normal in the .H file, as we are doing with Hello.H. Menu Attributes The attributes are a small enough number I will list them: - 20 - 1) CHECKED - displays a check mark to the left of the menu text 2) GRAYED - grays out the text, and is also INACTIVE 3) INACTIVE - does not gray out the text, but sends no message to the application 4) MENUBREAK - This item, and following appear in a new column 5) MENUBARBREAK - This item, and following appear in a new column, separated by a vertical line In order to keep the main menu standard, I didn't add any of this stuff to it, however, there is a secondary menu, "Hello2", defined in hello.rc that shows GRAYED and CHECKED items. I took one slight excursion from the "standard" menuing, and to the 'Sample' popup, I added: menuitem separator menuitem "&Alternate..." IDM_ALTERNATE Alternate is used in Hello.c to bring up the secondary menu. When the second menu is being used, Grayed/Ungrayed, and One/Two show the use of the attributes in your code. System Menu The system menu is not really there for us to add to, but many people do, and it is definitly a quick way to get an about box up. Since the menu is not defined in our code, we need to add to it at run time. This is done by using the AppendMenu command. A menu pointer (of type HMENU) is declared, and a pointer to the System Menu is captured: hMenu = GetSystemMenu(hWndMain, FALSE); Then the append is accomplished as follows: AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); AppendMenu(hMenu, MF_STRING,IDM_SETUP, "Setup..."); I like to put a separator (a horizonal line) between the default setup items and anything I add. There are a few things that need to be handled when using the system menu: 1) the IDs used must be < F000H to avoid conflict with the normal system menu commands 2) any WM_SYSCOMMAND messages not captured must be passed to the default window processor, or else they won't get executed. From hello.c, in WndProc, we find the following code: switch (message) - 21 - { . case WM_SYSCOMMAND : switch (wParam) { case IDM_SETUP : lpfnHelloDlgProc = MakeProcInstance(HelloDlgProc, hInst); DialogBox(hInst, "Hello", hWnd, lpfnHelloDlgProc); FreeProcInstance(lpfnHelloDlgProc); return 0; } break; . } return DefWindowProc(hWnd, message, wParam, lParam); the message passed is WM_SYSCOMMAND if a menu item from the system menu is selected. The item selected is passed in wParam. Therefore, a second switch case is necessary to capture the system menu commands. In the above example, IDM_SETUP is handled locally by displaying the setup dialog box, but any other system commands are sent along to the default window procedure DefWindowProc. This will ensure that nothing is lost. You could 'trap' out certain system menu commands at this point by executing the function yourself and returning 0. the normal system menu options and their IDs are: Restore SC_RESTORE Move SC_MOVE Size SC_SIZE Minimize SC_MINIMUM Maximize SC_MAXIMUM Close SC_CLOSE Switch To SC_TASKLIST Menu Modification Just as the system menu was appended above, there are other commands that may be executed to modify menus: 1) AppendMenu 2) DeleteMenu - deletes a menu item, and destroys the item 3) InsertMenu - Inserts an item 4) ModifyMenu - changes an item 5) RemoveMenu - removes an item from a menu Delete Menu would destroy a POPUP menu for example, and RemoveMenu would only remove the ability to bring it up from the main. - 22 - Hello.C Now let's look a little closer at the code from Hello.C this month that handles menus: As explained above, the main application menu is declared at class-registration time of the window. That's all it takes to put the menu up in your app. In the WM_CREATE case of WndProc, we added the line: hMainMenu = GetMenu(hWnd); This gives us a handle to restore back to the main menu "Hello", after we have been using the alternate one. We normally wouldn't have had to capture this handle. WM_COMMAND is where the action all takes place. wParam is the menu entry selected by the user. Since we are going to be messing around with the attributes in the sub-menu, we capture a menu handle inside this case statement for that purpose. For the menu items, all I did was to setup a print line for each menu entry, and fall through to the MessageBox routine at the bottom. IDM_ALTERNATE simply executes a SetMenu command which switches us to the alternate menu defined in hello.rc. ALTONE/ALTTWO and ALTGRAY/ALTUNGRAY use a brute-force method of toggling each other on and off. IDM_ALTRESTORE restores the main menu we started with. Since we have only one Windows Application, you'll notice that independent of which menu is alive at the time, we still have to handle all the cases for both, all the time. End. I think it probably takes two issues before the feedback comes in, so this column is running slightly open-loop at this point. In other words, I am still steering. I want some help steering, so get me some feedback. In particular, I am looking for ideas on a useful WinApp we can build from scratch explaining as we go, hopefully building off what we've already done here. I have one correction, or addition to last month's article. I talked about Borland programmers needing to add: #define WINVER 0x0300 ahead of your include of windows.h in your hello.c file to compile under the 3.1 compiler for Windows 3.0. This holds true for Microsoft programmers - 23 - also. Additionally, changing wc.lpfnWndProc = WndProc; /* Name of proc to handle window */ to be: wc.lpfnWndProc = (WNDPROC)WndProc; /* Name of proc to handle window */ holds true for Microsoft, too. One other thing I noticed is that I held a pretty hard line on not going beyond the page width for long lines. Since that was my first article, I didn't realise that I had a line length of about 75, and while I was complaining about long lines, I was displaying them, too...sorry!! Please hang in there. If you are beyond the scope of this article, stick with me - we are going to go places together. If you are way beyond this, write us an article. If you are bogged down, just compile it, and stare at the source. If you want help with something, send me a note. That's it for this time. Next month we're going to add a file list box the hard way, and discuss list boxes in general, and how to manipulate text/filenames in them. Feel free to contact me in any of the ways below. I want to rat out the things other people are having questions about, not just what I think people want to hear. Dave Campbell WynApse PO Box 86247 Phoenix, AZ 85080-6247 (602)863-0411 CIS: 72251, 445 Phoenix ACM BBS (602) 970-0474 - WynApse SoftWare forum - 24 - Owner-Drawn List Boxes By Mike Wallace If you've ever wanted to liven up your list boxes with color, then you need to know about owner-drawn list boxes. I recently wanted to create a list box and color-code each item in the list based upon its value. The only way to do this is with an owner-drawn list box, which is just what the name implies - the owner (i.e., the programmer) has to draw the list box himself. It's a little bit of work, but well worth the effort. The basis for any owner-drawn control is the DRAWITEMSTRUCT structure, which is declared as follows: typedef struct tagDRAWITEMSTRUCT { UINT CtlType; UINT CtlID; UINT itemID; UINT itemAction; UINT itemState; HWND hwndItem; HDC hDC; RECT rcItem; DWORD itemData; } DRAWITEMSTRUCT; The owner of the owner-drawn control receive a pointer to this structure as the lParam parameter of the WM_DRAWITEM message. This structure is described completely in the Microsoft documentation, but I want to describe a few (the important ones) so you won't have to get out your manuals just to read this article: hDC : the device context for the list box (very important) itemID : the index of the itme in the list box itemAction : describes the drawing action required by the owner and will have one of the following values: ODA_DRAWENTIRE : The entire list box needs to be drawn ODA_FOCUS : The list box has gained or lost focus ODA_SELECT : The selection status has changed itemState : describes what the item will look like after it's been drawn; it will have one of the following values: ODS_CHECKED : The menu item is to be checked ODS_DISABLED : The item is to be drawn as disabled ODS_FOCUS : The item has input focus - 25 - ODS_GRAYED : The menu item is to be grayed ODS_SELECTED : The item has been selected by the user itemData : contains the value last assigned to the list box by an LB_SETITEMDATA message. If the list box has the LBS_HASSTRINGS style, this value is initially zero; else, this value is initially the value passed to the list box in the lParam parameter of either the LB_ADDSTRING or LB_INSERTSTRING message. Enough small talk. On to the program... For this example, I'm using a dialog box containing only a list box and an "OK" button. If the user selects an item in the list box, give the item a different color. In the function handling this dialog box, we only care about the following four messages: WM_INITDIALOG, WM_COMMAND, WM_DRAWITEM and WM_MEASUREITEM. Here's a brief description of how the program will handle each of these messages: WM_INITDIALOG : The dialog box is getting created, so we have to do two things: a) use the GetDlgItem() function on the list box control to get a handle to the list box; and b) use that handle to fill the list box with an array of strings using the SendMessage() function and the LB_ADDSTRING message. WM_COMMAND : If the user presses the OK button, exit the function. WM_DRAWITEM : If the status of any item in the list box gets changed (including the initial filling of the list box), this message gets sent to your function. Check the value of the itemAction field. For this example, I'll handle the ODA_DRAWENTIRE and ODA_SELECT cases with the same code that handles both the selected and unselected states. For the ODA_FOCUS case, I will just call DrawFocusRect(). WM_MEASUREITEM : This message gets sent so you can set the height of the items in the list box. Create a device context for the dialog box and then use the GetTextMetrics() function to determine the font height. The definition for the dialog box also needs to be discussed. Primarily, the list box must have either the LBS_OWNERDRAWFIXED or the LBS_OWNERDRAWVARIABLE styles. The latter allows each item to have a different height. I'm using the former for this program. Here's the "ODLBOX.DLG" file: ODLISTBOX DIALOG LOADONCALL MOVEABLE DISCARDABLE 60, 25, 120, 120 STYLE WS_CAPTION | WS_DLGFRAME | WS_POPUP CAPTION "Owner-Drawn List Box" BEGIN CONTROL "", IDB_LISTBOX, "listbox", LBS_OWNERDRAWFIXED | - 26 - LBS_HASSTRINGS | LBS_STANDARD | WS_VSCROLL | WS_GROUP | WS_TABSTOP | WS_CHILD, 25, 10, 75, 85 CONTROL "OK", IDB_OK, "button", BS_DEFPUSHBUTTON | WS_TABSTOP | WS_CHILD, 40, 100, 40, 14 END I've defined ODLISTBOX, IDB_LISTBOX and IDB_OK in the "ODLBOX.H" file. All that leaves is the code for the function itself. Here it is: // Function to handle an owner-drawn list box #include #include "odlbox.h" BOOL FAR PASCAL ODListBox(HWND hDlg, unsigned message, WORD wParam, LONG lParam) { // define some colors for the selected text and background #define GREEN RGB(0,255,0) #define BLUE RGB(0,0,255) HWND hLst; // handle to the list box HDC hDC; // device context handle LPDRAWITEMSTRUCT lpDIS; // long pointer to DRAWITEMSTRUCT LPMEASUREITEMSTRUCT lpMIS; // long pointer to MEASUREITEMSTRUCT TEXTMETRIC tm; // text info structure HBRUSH hBrush; // draws text background DWORD OldTextColor; // saves old text color DWORD OldBkColor; // saves old background color int NumItems; // number of items in list box int i; // temporary variable char textStr[80]; // list box item string buffer char *textItems[] = { // strings going into the list box "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten" }; NumItems= 10; switch (message) { - 27 - case WM_INITDIALOG: hLst= GetDlgItem(hDlg, IDB_LISTBOX); for(i = 0; i < NumItems; ++i) SendMessage(hLst, LB_ADDSTRING, 0, (LONG) (LPSTR) textItems[i]); SetFocus(GetDlgItem(hDlg, IDB_OK)); break; case WM_COMMAND: if(wParam == IDB_OK) { EndDialog(hDlg, NULL); return TRUE; } break; case WM_DRAWITEM: // copy the DRAWITEMSTRUCT pointer from lParam lpDIS= (LPDRAWITEMSTRUCT)lParam; // if no items in the list box yet, set focus if(lpDIS->itemID == -1) { DrawFocusRect(lpDIS->hDC, (LPRECT)&lpDIS->rcItem); return TRUE; } // draw items in list box and check for selection if((lpDIS->itemAction & ODA_DRAWENTIRE) || (lpDIS->itemAction & ODA_SELECT)) { // Get the text string and save in textStr SendMessage(hLst, LB_GETTEXT, (WORD)lpDIS->itemID, (LONG) (LPSTR) textStr); // Handle the selection state if (lpDIS->itemState & ODS_SELECTED) { // text was selected, so make it green on blue // first, draw and fill background hBrush= CreateSolidBrush(BLUE); FillRect(lpDIS->hDC, (LPRECT)&lpDIS->rcItem, hBrush); DeleteObject(hBrush); // set colors and output the text OldTextColor= SetTextColor(lpDIS->hDC, GREEN); OldBkColor= SetBkColor(lpDIS->hDC, BLUE); TextOut(lpDIS->hDC, (int)(lpDIS->rcItem.left), (int)(lpDIS->rcItem.top), (LPSTR)textStr, lstrlen(textStr)); - 28 - // restore old colors SetTextColor(lpDIS->hDC, OldTextColor); SetBkColor(lpDIS->hDC, OldBkColor); } else { // item not selected, so make it black on white // first, draw and fill background hBrush= GetStockObject(WHITE_BRUSH); FillRect(lpDIS->hDC, (LPRECT)&lpDIS->rcItem, hBrush); // next, draw the text TextOut(lpDIS->hDC, (int)(lpDIS->rcItem.left), (int)(lpDIS->rcItem.top), (LPSTR)textStr, lstrlen(textStr)); } // Check for focus state if(lpDIS->itemState & ODS_FOCUS) DrawFocusRect(lpDIS->hDC, (LPRECT)&lpDIS->rcItem); return TRUE; } if(lpDIS->itemAction & ODA_FOCUS) { DrawFocusRect(lpDIS->hDC, (LPRECT)&lpDIS->rcItem); return TRUE; } break; case WM_MEASUREITEM: /* get and use height of current font */ lpMIS= (LPMEASUREITEMSTRUCT)lParam; // copy info ptr hDC= GetDC(hDlg); // create a DC GetTextMetrics(hDC, &tm); // get text info lpMIS->itemHeight= tm.tmHeight; // get text height ReleaseDC(hDlg, hDC); // free the DC return TRUE; } /* switch(message) - end */ return FALSE; } /* ODListBox() - end */ - 29 - Beginner's Column for Turbo Pascal for Windows By Bill Lenson Welcome one, welcome all to the first of, hopefully, many beginner's Pascal columns. If you've just purchased Turbo Pascal for Windows (TPW) or had it for a while I think this column will be of interest to you. I'll try not to go too fast so everyone gets lost but not too slow that nothing gets said either. Hey, I'm a programmer but I haven't forgotten what it's like learning a new language. Pascal is different from most languages and I think it will be a lot of fun. How much computer experience do you need to understand this stuff? Not much. You should have a basic grasp of USING windows and know enough DOS (or Windows File Manager if you prefer) to make directories and copy files. That's it. Everything else will be covered in the articles. All too often I've seen people try to learn a language like Pascal and ultimately get frustrated after a month or two. Their problem is simple: they may have picked up some really neat tricks to develop simple programs but they lack the fundamentals needed to soundly write large programs. To be a really good Pascal programmer you will need to learn lots of really scary things. First you will need to learn the fundamentals of the language. This isn't TOO bad because Pascal programs are fairly easy to read. Next, you'll need to learn algorithms and data structures. These are the building blocks of any program. Thirdly, given a problem, you'll need to know how to analyze it and produce a program to turn concept into reality. Finally, you must learn all the extensions your particular environment offers to Pascal. For this column we're using Windows so you'll need to become aware of the thousand or so functions available to you in Windows. To complicate matters, you can't effectively learn these things one at a time. To be a successful learner, you need to be exposed to everything at once but not so much of each that you'll run screaming into the night. Have I scared anyone? I hope not. Pascal isn't a difficult language to learn if you take it in small steps. I was fortunate enough to start learning "3rd generation" languages out of a BASIC book in 1981 which introduced the language in 7 steps. Two years later I started learning Pascal in University. By applying small, logical steps (or at least I should have - hindsight is 20-20) I could have dramatically reduced the learning in my first four month course into a couple of weeks. Pascal is a much more robust language than BASIC and will require probably three times that many steps to learn. Hey, I was looking for a name for this column. "Pascal in 21 Steps". I like it. Anyway, the emphasis in each step (column) will be to study problem solving as you learn the language and introduce Windows extensions as you go. That's it for the first Turbo Pascal for Windows column. Next month we'll start in on the writing your first Pascal program. Things will really start to move then. See you next month. - 30 - Bill Lenson holds a Bachelor of Computer Science from the University of Windsor. He is currently employed at Price Waterhouse Canada as a consultant in their National Technology Services department. Bill is a team leader in the development of a major Windows software package written in C++ and Pascal. - 31 - Hacker's Gash This month's column is by Wim Harrison (hope I have the last name right; I forgot to write it down after downloading his note). 4 States of a button -------------------- I read with great interest, and mounting despair, the article about bitmapped buttons in WPJ 1-3. Yet again we have someone (just the latest in a long list) repeating the MS line that only 3 button states are needed. NOT! A button has 4 states - Normal, Focussed-not pressed, Focussed- pressed, and disabled (count 'em - 4). Everyone seems to omit the last state - disabled. What I do is to have a normal button (not focussed, not pressed) but with the international 'no entry' red diagonal bar across it. Then if you do a call to 'EnableWindow( hButton, FALSE)' it immediately acquires a red bar to show it is not available. Even Borland don't catch this, and I think it adds greatly to the user friendliness of the system. I have included the source (the Pascal version, I have C code as well) for the code I use. I use the following: ID + 100 = Normal ID + 200 = Focussed ID + 300 = Pressed ID + 400 = Disabled - the special one. Note it takes only one extra line to handle this ------------------------ Cut here -------------------------- PROCEDURE DrawControl( hWind : HWND; lpInfo : PDRAWITEMSTRUCT ); BEGIN IF ( lpInfo^.CtlType <> ODT_BUTTON ) THEN Exit; ResID := ( lpInfo^.CtlID MOD 100 ); Inc( ResID, 100 ); { } IF ( lpInfo^.itemState AND ODS_DISABLED ) <> 0 THEN Inc( ResID, 300 ) ELSE IF ( lpInfo^.itemState AND ODS_SELECTED ) <> 0 THEN Inc( ResID, 200 ) ELSE IF ( lpInfo^.itemState AND ODS_FOCUS ) <> 0 THEN Inc( ResID, 100 ); hBM := LoadBitmap( System.hInstance, MAKEINTRESOURCE( ResID ) ); IF ( hBM = 0 ) THEN - 32 - Exit; IF ( ( lpInfo^.itemAction AND ODA_DRAWENTIRE ) <> 0 ) OR ( ( lpInfo^.itemAction AND ODA_FOCUS ) <> 0 ) OR ( ( lpInfo^.itemAction AND ODA_SELECT ) <> 0 ) THEN BEGIN hMemDC := CreateCompatibleDC( lpInfo^.hDC ); hOldbm := SelectObject( hMemDC, hBM ); IF ( hOldbm <> 0 ) THEN BEGIN StretchBlt(lpInfo^.hDC, lpInfo^.rcItem.left, lpInfo^.rcItem.top, lpInfo^.rcItem.right - lpInfo^.rcItem.left, lpInfo^.rcItem.bottom - lpInfo^.rcItem.top, hMemDC, 0, 0, 63, 39, SRCCOPY); SelectObject( hMemDC, hOldbm ); END; DeleteDC( hMemDC ); END; DeleteObject( hbm ); END; ------------------------------ CUT HERE ------------------------------- This is called from the main (dialog) proc to handle all custom button draw requests. BTW the 63, 39 values above are because I use 64x40 buttons. - 33 - Microsoft's Windows Strategy By Pete Davis Well, in March I got the honor of going to a Microsoft Windows Strategy seminar put on by our buddies over at Microsoft. It was actually pretty interesting (any seminar goer can tell you that most are real sleepers.) I have to admit that at any seminar, the temptation to cop some Zs is pretty high and this one was no different, but there was a lot of interesting stuff for me to relay. (Otherwise I wouldn't be writing this, right?) What are the big things in Microsoft's future? Well, OLE 2.0 is probably at the top of the list, according to Microsoft. This is both a good thing and a bad thing. They showed a very impressive demo of OLE 2.0 in action and I think everyone there was ooing and ahing. But, as I said, there is also a bad side, that being that OLE isn't the easiest thing in the world to program. Seeing as that's the case, we're obviously going to have to cover it here at some point and I'll start looking into that myself. My personal opinion, especially after talking to a few experts, is that OLE 2.0 won't really pan out the way Microsoft wants. In all honesty, it's just too much of a pain to program and it's very document oriented. Microsoft might try to argue otherwise, but I don't think EVERY program can be considered document-based. Microsoft, for some reason, thinks they are. Maybe I'm wrong, but we'll see in the near future, won't we. What can OLE 2.0 do for you? OLE 2.0 is VASTLY improved over 1.0. If you're familiar with 1.0, picture this. Let's say you were in Microsoft Word and you have an OLE link to an Excel spreadsheet. Let's say that link shows maybe 5 columns and 3 rows. Well, in 1.0, when you double clicked on the Word version, boom, Microsoft Excel pops up a big old window with your spreadsheet. Not too bad, but try to picture the 2.0 version. You double click on your link and boom, nothing happens. Well, not exactly nothing, but it's real hard to notice. First of all, the title line that used to say 'Microsoft Word ....' whatever, has now changed to 'Microsoft Excel ....'. The menu bar has changed to the Excel menu bar. Any tool bars are changed over to Excel tool bars. The column headers A-F and row headers 1-3 show up right around your existing copy of the spreadsheet. (Not the entire Excel window.) The weird thing is, the rest of your document is right there, unchanged. It looks like you're still in Word. The most noticable thing is the column and row headers which suddenly surround your spreadsheet. If you have bitmaps and graphs and stuff in your document, and they were on the screen before you double-clicked, they're still there after you double click. Pretty neat, eh? On top of all that, let's say you have, not only a few rows and columns, but also, say a link to a graph based on the numbers in your spreadsheet, in the document. Well, again, you can double click on the graph, and almost magically, you're in Excel and you hardly noticed the change. Let's say you modify the values in the spreadsheet. Well, the graph changes immediately. Let's say you move the bar graphs bars? The numbers - 34 - change to coincide with the graphs changes. It's really pretty cool. Well, can't just rave about the great parts, writing code to do it can't be easy. Haven't seen the API for OLE 2.0 yet, but I've seen it for 1.0 and it isn't exactly a piece of cake. You can be sure 2.0 isn't going to be easier. What were some other things.... 'Cairo', that's what. If you're around Microsoft types, you've heard of Cairo for some time now. Cairo is the secret (and I use that in terms of how secret anything in this country usually gets) codename for Microsoft's 'Advanced' operating system. That was about all I've known for a while. Got a little more news about it, though. Cairo is going to be the next version of Windows NT, essentially. It's going to be very object oriented, more so than the existing operating system. What's more is Cairo is going to be based heavily on the use of OLE 2.0, so it's time to start brushing up on those OLE 2.0 skills. Microsoft is going to be supporting 5 versions of Windows, over the next few years. They are, from bottom to top, Modular Windows, Windows 3.1, Windows 3.1 for Workgroups, Windows NT, and Windows NT Advanced Server. Obviously, this list could be kinda grouped into a set of 3 operating systems, but I'm giving it to you the way they fed it to me. Anyway, we all know what Windows 3.1 is, I hope, and just think of 3.1 for Workgroups as the network version with some nifty mail packages and stuff. There, two down and three to go. Modular Windows is going to be the kinda odd one. It's going to be for several different types of hardware, including some sort of Nintendo-like box, though it's purpose won't be to be a game machine, but in the sense that it plugs into the TV and has a little controller and no keyboard. It's also going into the Pen computers and such. They had a long list of things, but no, I couldn't find Windows for Toasters in there, though they did make a passing reference about Fax machines, and I don't know about you, but I can dial a phone and press start without Windows, myself. Maybe it's for the thinking impaired. Modular Windows is essentially a cut down version of Windows 3.1. It has a subset of the API and a bunch of DLLs and unnecessary stuff are removed. The idea is to get it down to 1 meg. They said they'd be specializing it for each machine it was used on to keep the size at a minimum. You could tell that they were really counting on getting computers into every household, one way or another. Actually, some of the ideas sounded pretty cool and they're actually working with different vendors to try to make some of it a reality. For example, one demo they showed was a program for working with Cable TV. You could watch Cable, but let's say you were watching MTV, and a band you liked was on. Well, with a little extra bandwidth that's not used by the TV, but is used by your little Windows Cable Box, you have access to all kinds of information, like what the name of the band is, a list of albums and songs, when they'll be playing in your area, and if you want, you can click on a button to order your tickets. Want the CD? Click on another button and they'll send it to you and bill you through the cable company. Now, I don't want to sound like one of those idiots from the 50s who - 35 - thought everyone in the 70s would have a helicopter in there back yard, but I really think this kinda thing is going to happen. (By the way, later I'll be coming back to that helicopter thing, 'cause I got a bit to say about it.) They're actually working with cable box manufacturers and cable companies to make this stuff a reality. It's some pretty neat stuff and it's about time! They're saying they think this kind of stuff will be available, large-scale, by '95. Well, I've never been too fond of Microsoft's predictions, as far as timing, so I'm going to take a safe bet with '97, but I definately think it'll be there. Then there's NT. See my column on NT programming yet? Check it out. I'm clueless when it comes to NT programming, therefore, over-qualified to be teaching it. Anyway, their NT stuff was pretty impressive. Looks like they're already heavy into building add-on stuff, like SNA servers (by the way, that's the only thing I really thought OS/2 had on NT, not anymore, I guess.) They're really pushing the idea of programming with the Win32 and Win32s APIs and making everything portable between all the Windows systems. Good luck, I say. I'd like to see it, but people are always going to try to exploit the specific nooks and crannys that each different version has to offer, and that's just life. NT, staying or going? It's here to stay. It's not going to be on every desktop and Microsoft knows it, hence the Win32s API, but it will be fairly popular. It's going to be best for workstation stuff, software development, servers, and the like. Personally, I love NT as a development environment. Compile your 3.1 programs in the DOS box and test them under NT. If they crash, who cares, you don't have to re-boot, ever. Great time saver. Microsoft's going crazy. API-crazy, that is. They must have mentioned 6 or 7 of them while I was awake. They've got the Sockets API, Open Database Connectivity (ODBC) API, SNA API, Win32 and Win32s API, Mail API (MAPI), and, of course, PAPI, the Pizza API. Actually, the last was a joke about an API to order Pizza for you, but I wouldn't be suprised to see it. They've got APIs left and right and what I want to know is, who's going to have time to learn half of them? When we start sending out resumes, we're not going to be writing that we know how to program for Windows, we're going to be listing the APIs we know how to program for Windows. The ones I listed above are just the tip of the iceberg. There are a ton of them out there. In an effort to help consolidate on some of this, Microsoft has come up with the SPI, or Service Provider Interface. This is sort of an API for the APIs. The ODBC API is a good example of how this works. What happens is you program for the ODBC API. Then, the guys that write the database software, like Oracle, Sybase, DBase, and all those guys, write the SPI. That way, no matter which database program you're using the you just have to write for the ODBC API. Sounds good, right? Well, yeah, that's fine, but I've been programming for a long time, and I know programmer's. The first thing they're going to do is get a hold of a bunch of SPIs, find neat little tricks they can use that require them to bypass ODBC, because it won't support them or something, and then have these 'hard-wired' tricks for specific databases. Maybe I'm wrong but people have been talking standards for years and what do we have? Undocumented DOS and Undocmented Windows. People are always going to be bypassing these standard things so - 36 - they can make their programs jump through hoops. The worst case of this was someone who was talking to me about writing a standard set of APIs for programming in a GUI environment that would then have the equivelent of SPIs (remember those) for each GUI, like Windows or X-Windows. Then you'd just have to program for one API and it would re- compile for each different GUI interface. Yeah, right. Sorry, it'll never happen. What happens when you want to use NetBIOS? Sorry, X-Windows doesn't have NetBIOS, so your universal API won't either, so you're S.O.L. Anyway, just wanted to make that clear that that will not happen. Ok, so back to Microsoft going API crazy. Well, I have to give them an 'A' for effort. They're really busting their collective hump to get tools out there for us developpers. Over the next year or two, you can expect to see a rich collection of APIs to do everything from getting data off of a Nintendo machine via smoke signals to APIs for ordering Pizza. Microsoft's really trying to standardize and I applaud their effort, but I've seen a lot of people try to do that in this industry, so I remain skeptical. Another new catch-phrase (or catch-acronym, as the case may be) is WOSA which stands for Windows Open Services Architecture. This includes a lot of the API and SPI stuff. WOSA is the collection of all these front- ends (APIs) with changeable back-ends (SPIs) for different engines. I just felt a need to throw the catch-acronym at you. I didn't want you to hear it a few weeks later and then say, 'That's what Pete was writing about, but he didn't call it that. What an idiot!' Well, I'm not an idiot, I'm just stupid. Well, that about wraps it up. What do I think about all of this? Do you care? Do I care? Cool. You Better. Of Course. In that order. I like the idea of having a ton of APIs to play with. I've always been a bit of an API nut, so I'll be messing with everything I can get my hands on. I think Microsoft's going in the right direction and I think Bill's going to keep raking in the bucks. I hope the cable-tv stuff happens and I hope I can run Windows on my coffee maker in the near future. I can't make good coffee, maybe Windows can. Hey, and maybe Windows to flush my toilet, wash my clothes and mow my lawn. That's not too much to ask for, is it? Well, you may be laughing but Bill's probably working these problems out in his head as you read this. We'll see if he can do it. P.S. Bet you thought I forgot about that helicopter nonsense. Well, I did, and after re-reading, I'm adding this. I'm convinced Man has evolved tremendously since the 50's. Let's face it, we all know, now, that everyone having a helicopter in their driveway is a ridiculous idea. Most drivers can't drive. Let's just say that if helicopters were that popular, I'd live underground. 'nuff said. - 37 - Accessing Global Variables Across DLL by Rod Haxton In the February issue of WPJ I wrote an introductory DLL article. This month I wish to share with the many readers of the WPJ of a way that I discovered by necessity of handling global variables across DLLs. I describe my approach as a 'kludge' but, to me it is a fairly good 'kludge'. Like anything done against the norm there are some risks; like receiving flammable E-mail about going against Windows standards, and possibilities that Microsoft will change some things and therefore in later versions my approach will not work. All of these are possibilities but, I can live with those for now. Why? Because I think that it is a feasible kludge and what I will show you is nothing that Windows has not been doing since version 2.0 and it has not changed yet. That is not to say that they will not change, but that for now it seems safe to say that they will not. Here's some background information on why I chose to use the method I came up with. Windows already supplies ways of handling global variables across DLLs, for instance, shared memory, passing far pointers and redeclaring the variables in the DLL. These methods are workable and I strongly suggest that they be used. Well, here's the background. I was given the task of converting two DOS applications over to Windows and making them into one package. My mission was to get it up and running as quickly as possible. The original DOS application made use of numerous global variables. So, I decided to do a straight port, and after demoing the port and management making their decision, I would convert the App over to DLLs. Well, the first two steps (ported and mgmt. decision) were completed 7 months later. So, now the big job was to fix up everything over to DLLs. This was no problem except for the numerous global variables. Hind sight told me I was stupid for not dealing with them initially. Handling the global variables the way Microsoft suggests would mean that a great portion of the code would have to be rewritten, and you know that there just is never any time to do that. So, I began to think to myself, and I remembered reading about Micheal Geary's FIXDS. The same logic he applied to not having to call 'MakeProcInstance' also applied to what I was thinking. The stack segment (SS) for DLLs is that of the calling application. And, the calling apps. DS == SS. Therefore, any global variables (CONST and BSS) defined in the application is shared with the DLL via SS. The only problem is that you have no clear way of accessing those variables. In Windows global variables in segments do not change their offsets, only the segments can be changed due to memory management. Thus, the offset values of the global variables will always be at the same location inside the segment. Knowing this, how do we determine those offsets? The answer is the MAP file that the compiler will supply for you. The MAP file lists all global symbols defined in the object file(s), sorted by name and a list sorted by address. With this information in hand we can make a file of all the global variables used in the program. The global list should use the same case as the actual definitions. Also, since the MAP file sorts the variables by name and thats what we are looking for, the matching variable names and their offsets, our list file should be sorted, this will speed - 38 - the search. I wrote a utility program that I call "getoff.exe", that searches the MAP file and creates an output constants file of the variables, i.e., GLOBAL HEADER FILE INPUT FILE FORMAT OUTPUT FILE FORMAT ------------------ ----------------- ------------------- int variableA; variableA #define VARIABLEA 0x0001 int variableB; variableB #define VARIABLEB 0x0002 int variableC; variableC #define VARIABLEC 0x0003 The outputted constant file is to be included in the DLL modules. I forgot to mention one thing. The global variables must be declared inside the DLLs also. The cleanest way would be to include them in a global header file. Now with the use of some in-line assembly code the global variables can be accessed and updated inside the DLL. #include "consts.h" #include "global.h" . . . /* this code sets up the variables inside the DLL */ _asm mov ax, ss:[VARIABLEA] _asm mov [variableA], ax _asm mov ax, ss:[VARIABLEB] _asm mov [variableB], ax _asm mov ax, ss:[VARIABLEC] _asm mov [variableC], ax /* now use the variables like normal */ variableC= variableB; variableB= variableA; variableA= variableC; /* when finish reasign the values to the global */ /* variables defined in the application */ _asm mov ax, [variableA] _asm mov ss:[VARIABLEA], ax _asm mov ax, [variableB] _asm mov ss:[VARIABLEB], ax _asm mov ax, [variableC] _asm mov ss:[VARIABLEC], ax This is all it takes to accomplish the task of using global variables across DLLs. I created (for my company's purposes) entry/exit routines that handle the assigning and re-assigning of the variables, only because our application has over 100 global variables. - 39 - Included with this document is the "getoff.exe" utility and its source, and source and executable example of a DLL accessing a global variable. To run "getoff" type 'getoff '. As I stated, this is only a kludge, and so this method should only be used if you do not have time to rewrite code and you have too many global variables. If you have the fortunate pleasure of creating your own application try to stay away from global variables, but if you must, handle them the way Microsoft suggests. Rod Haxton can be reached at (202) 269-3780. - 40 - Getting A Piece Of The Future By Peter Kropf A few months ago, a computer trade publication ran an article in a regular column stating that anyone can obtain a beta copy of Windows NT. It would cost $69 without hard copy documentation, or $399 with a document set. There have been other short blurbs that I had read which gave a very brief view of Windows NT. Here was a chance to get the real thing. While waiting for the package to arrive from Microsoft, a trip to the local book store turned up a book called "Inside Windows NT" by Helen Custer. The book was written during the development process. At times it seems to read like a marketing brochure, but on the whole, I enjoyed reading it. The first two chapters were outstanding. They gave a wonderful overview to the various features and concepts of Windows NT. The remaining chapters discuss the features and concepts in much more detail, but the detail still remained at the surface level. Most of the features described read like VMS, The Next Generation. For those of you that don't know, Windows NT's chief driver is David Cutler, the same man who drove the initial development of Digital's VMS operating system. After reading through the book, I was very anxious to try Windows NT. There were a few hurdles to overcome before getting the system up and running. The distribution media is CD-ROM only. What that means is you must actually have a CD-ROM drive. Some of us (namely me) are a little slow. Until the media, computer and user were all physically in the same place, it just didn't click. The first drive purchased is a bundled package, one of those multimedia upgrade kits. Sound card/SCSI port/CD-ROM all in one. Great. Well, not so great. The SCSI port is not on the list of those currently supported by Windows NT. That means Windows NT can not be installed directly from the CD-ROM. Instead, all the required pieces are copied to a temporary working directory and a bootable starter floppy is created while running under MS-DOS. The system is then booted from the floppy and the installation of Windows NT actually begins. Once the installation is complete, Windows NT is booted directly from the hard disk. But, the SCSI port on the sound card is not currently recognized by Windows NT, therefore the CD-ROM drive cannot be accessed. That isn't really too bad until an attempt is made to change the configuration. It tries to read from the temporary load directory. But that directory was deleted after the installation completed! (The solution here was to boot under MS-DOS and copy the needed files into a directory that NT can then read.) What's that you say? Most hardware vendors have a 30 day money back guarantee/exchange policy? Gee you're right. Back the upgrade kit goes. A faster drive which is accessible from NT is required. Why? Well I don't know. Just because, that's why. An Adaptec 1542B with a Texel CD-ROM drive does the trick. Now my multimedia windows applications can access the CD-ROM directly while running under NT. Of course a sound card is now needed to allow complete multimedia. - 41 - After the installation completed, poking around the directories reveals some interesting things. There are a few new files in C:\ which are required by NT. WINNT Directory structure for NT. NTDETECT.COM Machine boot process NTLDR Machine boot process USERS Directory structure for NT users. BOOT.INI NT initialization information CONFIG.NT NT's version of CONFIG.SYS AUTOEXEC.NT NT's version of AUTOEXEC.BAT PAGEFILE.SYS NT's primary page file. BOOTSECT.DOS Purpose unknown. Hidden file. When the machines boots, it goes through the standard hardware testing and then loads NTDETECT/NTLDR/BOOTSECT.DOS. (At this time, I'm not certain of the relevance of these three files, their order and use at boot time are still unclear to me.) A menu is displayed with the entries in BOOT.INI: Windows NT from C:\winnt and the previous operating system from C:\. This allows either one to be easily booted. A timer is set for 30 seconds by default to wait for a selection. After either the timer has reached zero or the user has selected an OS, the highlighted entry is booted. MS-DOS boots without any problems. It's nice to be able to easily select which system to use. Selecting NT to boot begins a much different process. First the kernel and device drivers are loaded. Then all the known file systems are checked. (NT supports FAT, HPFS and NTFS.) If the file systems need to be rebuilt or fixed, this is where it would happen. All the partitions here are setup as FAT. So far, the checking process has never complained and no repair work has ever been done. After everything is checked, NT begins working. A bitmap screen is displayed stating that this is Windows NT BetaOctober '92 Release. Once NT has started all the required processes, a message is displayed indicating that CTRL-ALT-DEL are to be pressed to login. The standard CTRL-ALT-DEL to reboot the system whenever a problem occurs is gone. NT is a true pre-emptive multitasking operating system. One of the things this being along with it is the fact that disk I/O is now buffered in memory. Powering down the system without first requesting buffers to be flushed will most certainly cause file corruption. Anyone whohas ever crashed a Unix system will understand. The CTRL-ALT-DEL sequence will also ensure that the real logon screen is being displayed, not an attempt at capturing passwords. Once signed on, most things look the same. There are several new features and programs available such as user maintenance, disk maintenance and event log review, but their explanations are for a later time. Pressing CTRL-ALT-DEL after signing on brings up a window which allows almost complete control over the work currently being done. A task can be stopped, the user can be logged off, the system can be shutdown, along with - 42 - a few others. Shutting down the system has two options, one for a complete poweroff, and the second to shutdown NT and then reboot. The second option behaves much like CTRL-ALT-DEL while running under MS- DOS, allowing the system to be shutdown and rebooted in the event of a major problem. This brings to a close a first look at getting NT running on a Intel 486-based machine. Since NT will run on many other hardware platforms including the Mips R4000 and Digital's Alpha, getting a system up and running on those machines might be a little different. The next article will take a look at the additional features and programs available from the Program Manager. - 43 - The "ClickBar" Application ClickBar consists of a Dynamic Dialog pair that allows C developers for Windows 3.0/3.1 to insert a "toolbar" into their application. Microsoft, Borland, etc. developers may display a toolbar or tool palette inside their application, according to a DLG script in their .RC or .DLG file. Borland developers may install ClickBar as a custom control, providing three custom widgets added to the Resource Workshop palette. These are three different button profiles defining 69 custom button images total. The buttons may be placed and assigned attributes identically to the intrinsic Resource Workshop widgets. Source is provided for a complete example of using the tools, including the use of custom (owner-drawn) bitmaps for buttons. Version 1.5 uses single-image bitmaps to reduce the DLL size, and includes source for a subclass example for inserting a toolbar. Registration is $35 and includes a registration number and printed manual. WynApse PO Box 86247 (602) 863-0411 Phoenix, AZ 85050-6247 CIS: 72251,445 - 44 - Getting in touch with us: Internet and Bitnet: HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (Pete) GEnie: P.DAVIS5 (Pete) CompuServe: 71141,2071 (Mike) WPJ BBS (703) 503-3021 (Mike and Pete) You can also send paper mail to: Windows Programmer's Journal 9436 Mirror Pond Drive Fairfax, VA 22032 U.S.A. In future issues we will be posting e-mail addresses of contributors and columnists who don't mind you knowing their addresses. We will also contact any writers from previous issues and see if they want their mail addresses made available for you to respond to them. For now, send your comments to us and we'll forward them. - 45 - The Last Page by Mike Wallace Got this in the mail today. WPJ has the coolest readers. Hope you like it. Date: 01-Apr-93 12:41 EST From: Mike Strock >INTERNET:MikeS@asymetrix.com Subj: Humor: Health Tips for the 90s BURNOUT PREVENTION AND RECOVERY 1. STOP DENYING. Listen to the wisdom of your body. Begin to freely admit the stresses and pressures which have manifested physically, mentally or emotionally. MICROSOFT VIEW: Work until the physical pain forces you into unconsciousness. 2. AVOID ISOLATION. Don't do everything alone! Develop or renew intimacies with friends and loved ones. Closeness not only brings new insights, but also is anathema to agitation and depression. MICROSOFT VIEW: Shut your office door and lock it from the inside if you can so no one will distract you. They're just trying to hurt your productivity. 3. CHANGE YOUR CIRCUMSTANCES. If your job, your relationships, a situation or a person is dragging you under, try to alter your circumstances, or if necessary, leave. MICROSOFT VIEW: If you feel something is dragging you down, suppress those thoughts. This is a weakness. Drink more coffee (it's free). 4. DIMINISH INTENSITY IN YOUR LIFE. Pinpoint those areas or aspects which summon up the most concentrated intensity and work toward alleviating that pressure. MICROSOFT VIEW: Increase intensity. Maximum intensity = maximum productivity. If you find yourself relaxed and with your mind wandering, you are probably having a detrimental effect on the stock price. 5. STOP OVERNURTURING. If you routinely take on other people's problems and responsibilities, learn to gracefully disengage. Try to get some nurturing for yourself. MICROSOFT VIEW: Always attempt to do everything. You ARE responsible for it all. Perhaps you haven't thoroughly read your job description. 6. LEARN TO SAY "NO". You'll help diminish intensity by speaking up for yourself. This means refusing additional requests or demands on your time or emotions. - 46 - MICROSOFT VIEW: Never say no to anything. It shows weakness, and lowers the stock price. Never put off until tomorrow what you can do at midnight. 7. BEGIN TO BACK OFF AND DETACH. Learn to delegate, not only at work, but also at home and with friends. In this case, detachment means rescuing yourself for yourself. MICROSOFT VIEW: Delegating is a sign of weakness. Don't let someone else do it (See # 5). 8. REASSESS YOUR VALUES. Try to sort out the meaningful values from the temporary and fleeting, the essential from the nonessential. You'll conserve energy and time, and begin to feel more centered. MICROSOFT VIEW: Stop thinking about your own problems. This is selfish. If your values change, we will make an announcement at the company meeting. Until then, if someone calls you and questions your priorities, tell them that you are unable to comment on this and give them the number for Microsoft Marketing. It will be taken care of. 9. LEARN TO PACE YOURSELF. Try to take life in moderation. You only have so much energy available. Ascertain what is wanted and needed in your life, then begin to balance work with love, pleasure and relaxation. MICROSOFT VIEW: A balanced life is a myth perpetuated by the Borland Marketing Team. Don't be a fool: the only thing that matters is work and productivity. 10. TAKE CARE OF YOUR BODY. Don't skip meals, abuse yourself with rigid diets, disregard your need for sleep or break the doctor appointments. Take care of yourself nutritionally. MICROSOFT VIEW: Your body serves your mind, your mind serves the company. Push the mind and the body will follow. Drink Mountain Dew (it's free). 11. DIMINISH WORRY AND ANXIETY. Try to keep superstitious worrying to a minimum - it changes nothing. You'll have a better grip on your situation if you spend less time worrying and more time taking care of your real needs. MICROSOFT VIEW: If you're not worrying about work, you must not be very committed to it. We'll find someone who is. 12. KEEP YOUR SENSE OF HUMOR. Begin to bring joy and happy moments into your life. Very few people suffer burnout when they're having fun. MICROSOFT VIEW: So, you think your work is funny? We'll discuss this with your manager on Friday. At 7:00 pm. - 47 -