Session 5

Adding right-mouse events, drag and drop, and a dialog box to the editor
(WM_DROPFILES, OnMouseDown, Multiple forms)

Session #5

This session I added some features to the editor that were brought up during last session. The two big ones are using the right mouse button to paint in the empty (background) color and to open a data file by allowing the user to drag and drop from Windows Explorer. I also showed how to add another form (an About box in the Help menu) to the application. I showed some other things as well, like using .ini files to persist values between invocations. However, I'm not going to put that here. I need to re-architect the application slightly in light of this enhancement. It will all be in the next session.

Painting with the right mouse button. There are really only two small places in the code that needed to be added/modified to support this. The first one is the addition of an OnMouseDown event handler. The second is a slight modification to the OnMouseMove event handler. You can compare last session's code to this one to see the changes. Look at the online help to see what information is stored in the TMouseButton and TShiftState parameters.

/*
  Handler for the OnMouseDown event. The Shift parameter contains
  additional information such as the state of the control/alt/shift keys.
*/
void __fastcall TfrmMain::gridWorldMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
    // If the right mouse button is pressed down
  if (Button == mbRight)
  {
      // Convert the x/y pixel location to a row and column in the grid
    TGridCoord coord = gridWorld->MouseCoord(X, Y);

      // Update current row/column
    FCurrRow = coord.Y;
    FCurrColumn = coord.X;

      // Set this cell as empty (background color)
    FDataMap[FCurrRow][FCurrColumn] = ctEMPTY;

      // Paint the cell
    HighlightCell(true);
  }
}

Supporting drag and drop from Windows Explorer. Handling files that are dropped onto the main window is straight-forward. After setting up some message-handling code, you simply need to implement the functionality you want to achieve. In this case, when a data file (collision file) is dropped onto the window, we want to load it into the editor. The behavior is exactly the same as if the user browsed for the file with the File Open dialog and selected a file from there. Drag and drop is much more natural (eventually!) than navigating the file system. These are the steps to remember:

  1. In the header file, add a declaration for a method that will handle the WM_DROPFILES message from Windows. You can put it in the private section. We will implement this in the .cpp file. Information about the WM_DROPFILES message can be seen here. (Keep in mind that Microsoft is notorious for moving pages on their website so you might have to search for it.)
    void __fastcall WMDropFiles(TWMDropFiles &Message);
    
  2. In the header file, add a MESSAGE_MAP to the form's class (private or public). This is boiler-plate code for Windows.
    BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WMDropFiles)
    END_MESSAGE_MAP(TForm)
    
  3. In the implementation file, register the application with Windows as being a potential drop-target. Put this in the form's OnCreate method:
      // Register this window with the OS as a drop target.
    DragAcceptFiles(this->Handle, true);
    
  4. (Optionally) In the implementation file, unregister the application with Windows as being a potential drop-target. Put this in the form's OnDestroy method:
      // Unregister this window with the OS as a drop target.
    DragAcceptFiles(this->Handle, false);
    
  5. Now, add the desired functionality by implementing the event handler in the implementation file:
    /*
      Handler for the WM_DROPFILES message from Windows.
    */
    void __fastcall TfrmMain::WMDropFiles(TWMDropFiles &Message)
    {
      int num_files, filename_length;
      char file_name[MAX_PATH + 1];
    
        // Refer to the Win SDK documentation about the details
        // of DragQueryFile and the HDROP structure
    
        // Get the number of files that are being dropped on the window
      num_files = DragQueryFile((HDROP)Message.Drop, -1, NULL, 0);
    
        // Unlikely to happen
      if (num_files < 1)
        return;
    
        // Retrieve each filename that was dropped
      for(int i = 0; i < num_files; i++)
      {
          // Get the length of the i'th filename
        filename_length = DragQueryFile((HDROP)Message.Drop, i, NULL, 0);
    
          // Retrieve the filename from the HDROP structure
        DragQueryFile((HDROP)Message.Drop, i, file_name, filename_length + 1);
    
          // Reset the grid
        Init();
    
          // Read in the data
        ImportMapData(file_name);
    
          // Update the UI
        short r = FRows;
        short c = FColumns;
        spnRows->Position = r;
        spnColumns->Position = c;
    
          // We can only deal with one at this point so bail out.
          // If we could handle multiple files, we would loop through all of them.
        return;
      }
    }
    
Adding a second form to the application. It is pretty easy to add additional forms to the program. In the first example, I simply added a pre-built "About dialog" that will be used to hold information about the program.
  1. Choose "File | New | Other..." from the File menu. Select the item in the left pane called "C++Builder Files". Select "About box" from the list on the right. (It should be the first one.)
  2. Now, add this class' header file to the main form. You can do this "manually" or you can do it by selecting the MainForm.cpp file in the IDE and from the File menu choose "Use Unit...". In the dialog box, choose "ABOUT". This process simply adds the About form's header file to your .cpp file.
  3. Finally, add the code that will display the About box.
    AboutBox->ShowModal();
    
    You can put this code anywhere. The obvious place is to make a Help menu that has an "About..." choice. When the user selects it, the dialog should be displayed. (See the project for details.) Also, if you want the dialog box to be non-modal, call the Show() method instead of ShowModal().

Exercises

  1. Implement a "cleaner" About dialog. Do this by giving better names to all of the components and by filling in the information in the dialog. This is an easy task, but it will give you good practice in navigating between the visual form and the code behind it.
  2. Make the About dialog resizable. Currently, it can't be resized, which is generally how this type of dialog should work. Keep in mind that you must make sure that the components also size/place themselves properly in the window as it is resized.