Session 10

(OwnerDraw, detecting multiple instances, memory-mapped files, DLLs.)

Session #10

  1. OwnerDraw Techniques

    Suppose that a visual component doesn't display information exactly the way you want it to? For example, in a tree view control (TTreeView), you can specify the properties of the font (TFont) used for the text. However, this property applies to the entire tree view. You can't specify the font that is used for each individual line in the tree view. Well, this is where the concept of "OwnerDraw" comes in. Basically, it means that the programmer takes control of drawing each item in the control, instead of letting the control do it automatically. This is actually a fairly easy thing to do, depending of course on what you actually want to do.

    In addition to changing the font properties (name, size, color, etc.) you can do things like wordwrap the lines in the tree view, or draw bitmaps within the text. Pretty much anything. Again, the more "radical" you want to get, the more work it is. Most of the time you will rarely need to draw things yourself. But, when you do, OwnerDraw is the mechanism.

    This application shows how to use the OwnerDraw style to display each line in a list box in a different font and/or color. It also shows how to draw each panel in the status bar in a different way. There isn't a lot of code or a lot of output displayed here, but the OwnerDraw technique is a very powerful concept that can come in handy when you need it. It will distinguish a good UI from a Really Good UI.

    All items have the same font and colorEach item has its own font and color


  2. Single-instance Applications

    This technique can be seen in many applications. It's the technique where, if a program is already running and you try to run another instance, instead of a second program running, the existing instance is brought to the foreground. This is made even more popular by applications that allow you to open more than one document at a time (e.g. any text editor other than Notepad.) For example, suppose you're running Notepad++ and you have a few files opened in it. Now, you double-click on another text file in Explorer. What happens (probably) is that instead of running a second instance of Notepad++ and loading the text file into it, the text file is loaded into another tab in the already-running instance of Notepad++. This just doesn't happen automatically. The program had to make this happen. (In the absence of a program preventing a second instance from running, Windows will always start another, completely separate, instance).

    There are 3 projects here that demonstrate how to deal with this. The application is a simple text editor that allows you to open multiple text files, each in its own tab. However, each version of the application handles multiple instances differently, from simple (do nothing), to more complex. Although the application is fairly straight-forward, there are a lot of little details that have nothing to do with the mulitple-instance issue. The zip files contain the complete project source for each example but I've only posted an HTML document of the file containing WinMain.

    1. The original application. If it's running and you try to run another instance, a second (or third, or fourth) instance will gladly open.
      Original executable    (MultiEdit.exe)
      WinMain file (MultiEdit.cpp) This is pretty much the same as all applications.
    2. Adding a little bit of code to the .cpp containing WinMain and it detects the presence of an existing instance and switches to that one instead of running itself. Immediately after switching to the other instance, the new instance terminates.
      Single-instance executable    (MultiEditSI.exe)
      WinMain file (MultiEditSI.cpp) This has a little additional code to detect running a second instance of the program.
    3. This is similar to the second version but it has a significant enhancement: Any information that was passed to the second instance will be forwarded to the original instance. This is important, for example, if the user tried to open a second instance of the text editor and load a text file into it. If all that happened was that the existing instance was brought to the foreground, the user would be confused as to why it didn't open the text file that was requested.

      You can easily see how this works by running an instance of the .exe file. Then, drop a text file on the .exe file in Windows explorer. This is the same thing as running it from a command prompt and typing the name of the text file on the command line.

      Single-instance executable    (MultiEditMM.exe) Forwards the command line to the existing instance using memory mapped files. A memory mapped file in Windows is similar to POSIX shared memory that you may have seen in CS 180. In other words, it's a form of interprocess communication (IPC), as the new instance is communicating with the existing instance by "handing off" the command line to it.
      WinMain file (MultiEditMM.cpp) This has more additional code to detect multiple instances and allow for data exchange between them. Uses memory mapped files.

      All 3 projects (Turbo C++) (XE 3) A zip file that contains a project group with all 3 projects ready to build and run.

    Although the focus of this section was on how to detect multiple instances of an application, there is another interesting aspect at work here. It's that this application is a very good starting point for a full-blown multiple-file text editor. You'll notice that you can open many files at one time, each in their own tab. There's an entire class called TTextEditor that encapsulates most of the work. (Each tab is created and holds an instance of a TTextEditor object). This class has many properties and also has a protected section, as it can be used as a base class for other editors.

    Other Uses For Command Line Arguments

    Even if you think that you aren't going to use the command line or that no one else is going to run your program from the command line, there are other reasons why supporting command line arguments will make your program much nicer to use.

    Uses in GUI Environments

    Have you ever dragged and dropped a file in Windows, Mac, Linux, or any other graphical environment? (Of course you have.) Well, a lot of the functionality of drag and drop is implemented via command line arguments. That's right, command line arguments.

    If you drag and drop a file onto an executable (or shortcut) in a graphical environment, you will notice that the executable is launched (run) and it opens the file that was dropped. So, if you drop a file onto Notepad.exe in Windows, Notepad will run and open the file that was dropped. This doesn't happen automatically. It works because the people that programmed Notepad provided a command line interface, which can also be used by GUI environments. (It's actually just another way to communicate with the program.)

    If you dropped a file named foo.txt onto Notepad.exe, the GUI environment is really doing something like this:

    Notepad.exe foo.txt
    
    which is very similar to how you would run Notepad.exe from the command line and pass the name of the file, foo.txt, to the program.

    Launching a Program from a Running Program

    Most students have learned a simple trick that executing a program from within their C/C++ code can be done using the system function. If your program was running and you wanted to execute Notepad.exe (Windows, of course) from your program and have it edit foo.txt, you would do this:

    system("Notepad foo.txt");
    
    This will load Notepad.exe and pass the filename, foo.txt. to Notepad. Notepad.exe will parse the command line and realize that it is being asked to load the file foo.txt.

    It is often a good idea to have your programs support command line arguments, even if you don't think that you or anyone else will ever actually execute the program from a command line. Command line arguments are a very simple way for one process to communicate with another and are supported on all operating systems in pretty much the same way.


  3. Putting the UI in a DLL

    Some of you may already know how to create and use a Dynamic Linked Library or DLL, for short. Time permitting, I usually give a brief overview of Windows DLLs in CS 280, as they are a very powerful programming mechanism. In a nutshell, a DLL is a way of sharing code (read: code re-use), without actually integrating the code with the executable. In other words, the libraries are "linked" to the program at run-time instead of the traditional link-time.

    There are many benefits of this technique, but one of the most important benefits is that you can share your C++ libraries (or a library in any language for that matter) with a program written in any other language. Got a blazingly-fast way of squaring a 100x100 matrix in C code? Put it in a DLL and you can use it in a C++, C#, D, Delphi, Visual Basic, Java, Python, Ruby, etc., program. Very powerful, indeed!

    In this demo, I will show you how to put your UI in a DLL that can then be used in any project you have. This is especially useful if you're working in a language or environment that doesn't provide a productive environment for UI development.

    The main UI in the executableThe UI from the DLL

    After clicking the "Get data" button in the main UI form or clicking the "Callback" button in the DLL form:


    The DLL (DLLProject.dll)

    The Executable (EXEProject.exe)

    Project files (with exe/dll) (Turbo C++) (XE 3) - Both project files to build it yourself (includes .exe and .dll)
    Project files (without exe/dll) (Turbo C++ only) - Both project files to build it yourself (without .exe and .dll)


  4. Non-rectangular forms

    This example program is more on the "lighter-side" of programming. Although, to be honest, I actually used these techniques on a shipping product back in the late 1990's. As it turns out, windows (forms) don't have to be rectangular. They can be any shape. Of course, you have to convey this information to Windows so that the form will be rendered properly.

    The examples below contain an image (TImage) of the state of Washington. The first one includes the normal borders and title bar. The second one has no border because the image is the form! You won't see a lot of these kinds of forms in business apps, but for "entertainment-like" apps you might.

    The form with a borderThe form with no border

    TODO: Finish this demo!

Exercises

  1. Challenge: The memory-mapped file only reserves enough space for a 255-byte string. (This is used to pass the command line from one instance to another.) However, if the second instance is invoked with a command line longer than 255 characters (which is possible), only the first 255 characters are used, which will likely cause odd behavior. Modify the structure used to communicate between the two instances in such a way that it can pass larger command lines. You don't have to make it handle any size, but something more than 255 would be nice. (Maybe 4K or something).

  2. The EXEForm has a button to toggle between showing the DLL form and hiding it. However, if it is showing and the user closes it by clicking the 'X' in the top-right corner, the EXEForm's button still says "Hide form". It still works correctly, but it would be nice if the DLL communicated with the EXEForm so it could update the button to read "Show form".