Under the Hood - Bring Window to the Top

by Bruce Johnson

Published 1998-01-01    Printer-friendly version

Download the code here

In 16 bit Clarion programs, if you run a second copy of a running application then the first copy comes to the front, and the second copy doesn't get started. Although this can be a real pain in some cases it certainly has its uses! Now, unfortunately, (or fortunately, depending on your point of view) 32 bit applications don't behave this way. They are allowed to launch multiple times on the same PC. This article deals with an easy way of stopping this from happening.

Essentially, after thinking for a short time, the problem becomes quite simple. When a 32 bit application is started it needs to:

  1. Discover if it is already running and, if it is, then tell the already-running application to come to the front.

And while it's running it needs to:

  1. Listen for other apps, and come to the front as directed.

The first part is not too difficult. While there are a number of ways of communicating between two programs in Windows, one of the easiest to implement (from a Clarion perspective) is DDE. Simply put, each program makes itself into a DDE Server. But when it runs, before it makes itself a server, it first tries to connect to itself ( ie it becomes a DDE Client ). If it can't become a client, then another copy of itself isn't already running and it carries on running itself (becoming a server in the process). If it can connect, then another copy of itself is running, so a message is sent to it.

So we're going to create 2 functions here, one for each section:

  1. Running - A function to see if an application is running or not and, if it is, then get it to come to the front.
  2. Listen - A function to listen for DDE Calls to come to the front and respond to them if they do come.

We're also going to see what bits of code we need to link these functions into our application.

Because I'm strongly in favour of re-using code between applications, I'm also going to demonstrate putting these functions into a template. In a previous article (The Joys of Reusable Code - Volume 1, Issue 1 of Clarion Online) I discussed how to make function libraries. You could, of course, put these functions into a library, and then just have a template to implement them. However, this time I'm going to show you another way of reusing code in the same application.

Lets start with the easy bits.

In the Global Program Setup embed point put the following;

IF Running()  ! Returns 0 if not already running
  RETURN      ! Closes the currently running application
END

This will run just after the program starts. The Running function, which we're about to write, will check for existing instances and send an instruction to them if they exist. Running will return a zero if it's the first instance of the program, and a positive number otherwise. So if it returns a number, this instance must quietly close - hence the Return command ( Remember we're in the main program bit here and a Return ends the program here. )

Lets look at the Running function a little more closely...

Running   FUNCTION
Channel   LONG
  CODE
  Channel = DDECLIENT(UPPER(COMMAND(0)))
  IF Channel <> 0
    DDEEXECUTE(Channel,'INFRONT')
    DDECLOSE(Channel)
  END
  RETURN(Channel)

Ok, it's quite short and sweet. Command(0) is an internal Clarion function that returns the name of the application currently running. Channel is a local variable that we use to store the result of the attempt to connect to the server. If the connection was successful then Channel will contain a value - if unsuccessful, then a zero. As this is the same as what the Running function is required to return, we can simply return it as-is. If the other instance is running, then we send it a command to come to the front.

Ok, so that's the client side of things out of the way; lets look at the server side. First, we need to add the following code to the After Opening the Window Embed point for the Frame of the app.

Co_Channel = DDESERVER(UPPER(COMMAND(0)))

Co_Channel is a Long and needs to be added to your Frame's Data variables.

That's all we need to do to make the frame of the app a DDE Server which will respond to calls from the later instances (the Clients). And all that's left to do is add the following also to the Frame, but this time in the Other Window Event Handling embed point.

Listen()

The Listen Procedure is defined as follows...

Listen    PROCEDURE
  CODE
  IF EVENT() = Event:ddeExecute
    IF DDEVALUE() = 'INFRONT'
      Target{prop:iconize} = 0
      BringWindowToTop(TARGET{prop:handle})
    END
  END

Nothing too special here, if the event is a ddeExecute event (which means some other application has sent us a ddeExecute command) and the ddeValue is INFRONT (i.e., the command that was sent) then we restore the window (in case it's minimised) and bring it to the front. The BringWindowToTop function is a Windows API call, and it takes the handle of a window. I'm using the Clarion built in variable Target and the property {prop:handle} which always points to the current window.

What's left to do? Well we still need to prototype the DDE functions and Windows API functions in the global map. So in the global embed pointInside the Global Map put the following:

  INCLUDE('DDE.CLW')
  COMPILE('***',_WIDTH32_ = 1)
    MODULE('Windows')
      BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
    END
  ***
  COMPILE('***',_WIDTH32_ = 0)
    MODULE('Windows')
      BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
    END
  ***

Wow, that looks like a mouthful. This is because in 16 bit mode then BringWindowToTop function takes a Ushort, and in 32 bit mode it takes a Ulong. So we make use of the Compile compiler directive to cater for both cases. From the point of view of this article we don't really need to do this (I'll leave you to figure out why) but it's best to do it right in case you use the function elsewhere. The function returns a byte and, as is usual for Windows API prototypes, we also use the Pascal and Raw attributes.

Ok so now we have all the bits in place, how do we make this truly reusable? Well there are a number of options:

  1. Add the Running and Listen functions to a personal library. This is the approach I've described in previous articles.
  2. Put all of this into a single template that can be added to any application.

I'm going to use the second method this time for two reasons. First, I've already described the first method and second, because we're all in the process of moving from Clarion 2 to Clarion 4. This method is more easily portable as the template should work as-is in both environments. (I say "should", because I haven't yet received my copy of C4 Gold so I can't test it. However if there are any issues involved then I'll post any updates to our web site at www.capesoft.com)

I'm going to describe the template in some detail to shed some light on this under-used area of Clarion.

First, select what kind of template it's going to be. Clarion offers 3 kinds of templates - a procedure template, a code template and an extension template. A code template is associated with some kind of window control, which we don't need in this case; a procedure template is used to make one procedure. We have more than one procedure to do, so I'm going to use an extension template, which EXTENDS applications or procedures, and that IS what we're doing here.

I'm going to call this template OnlyOne.Tpl and it needs to be stored in your \cw20\template directory for Clarion 2 users and in \Clarion4\template for Clarion 4 users.

Now remember, the goal here is to do it with a single extension, so the next decision to make is where will this extension go. It could be global or it could be attached to the frame. As we're going to modify the frame, and we can more easily modify the global from the frame than vice versa, let's attach it to the frame. First things first - let's start the template.

#TEMPLATE(OnlyOne,'Only 1 instance of 32 bit apps')
#EXTENSION(OnlyOne,'Make this app unique in 32 bit instances' )

Not so bad so far, nothing to be scared of ..... The #Template command is always there at the start of a new template class (A group of templates is called a class - we're only doing one template in this class, but we could have more if we wanted to.) And as we mentioned earlier, we're going to write an Extension template, hence the #Extension command. The rest of the template takes place in embed points. To do that we surround the code with #AT and #ENDAT commands. Let's do the global ones first...

#!-----------------------------------------------------------------
#AT(%CustomGlobalDeclarations)
#PROJECT('OnlyOne.Clw')
#ENDAT

This adds the OnlyOne.Clw module to your application as an external source module. We'll create the OnlyOne.Clw file after this template.

#!-----------------------------------------------------------------
#AT(%ProgramSetup)
If Running() ! Returns 0 if not already running
  Return ! Closes the currently running application
End
#ENDAT
#!----------------------------------------------------------------
#AT(%GlobalMap)
INCLUDE('DDE.CLW')
MODULE('OnlyOne.Clw ')
  RUNNING(),LONG
  LISTEN()
END
COMPILE('***',_WIDTH32_ = 1)
  MODULE('Windows')
    BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
  END
***
COMPILE('***',_WIDTH32_ = 0)
  MODULE('Windows')
    BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
  END
***
#ENDAT
#!----------------------------------------------------------------

I've already explained these two. Now for the embeds that affect the frame...

#!----------------------------------------------------------------
#LOCALDATA
Co_Channel LONG
#ENDLOCALDATA
That adds the Co_Channel to the local data.
#!----------------------------------------------------------------
#AT(%AfterWindowOpening)
Co_Channel = DDESERVER(UPPER(COMMAND()))
#ENDAT
#!----------------------------------------------------------------
#AT(%WindowOtherEventHandling)
Listen()
#ENDAT
#!----------------------------------------------------------------

And again, we've been through these bits already...

Last, we create the OnlyOne.Clw file which we'll put in the \cw20\libsrc directory (or \clarion4\libsrc directory if you're using Clarion 4)

  MEMBER('')
  ! -------------------------------------------
  ! Used by the OnlyOne.Tpl file and included
  ! Into apps where only one instance is wanted
  ! Even in 32 bits....
  ! -------------------------------------------
  MAP
    INCLUDE('DDE.CLW')
    MODULE('OnlyOne.Clw ')
      Running(),LONG
      Listen()
    END
    COMPILE('***',_WIDTH32_ = 1)
      MODULE('Windows')
        BringWindowToTop(ULONG),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
      END
    ***
    COMPILE('***',_WIDTH32_ = 0)
      MODULE('Windows')
        BringWindowToTop(USHORT),BYTE,RAW,PASCAL,NAME('BringWindowToTop'),PROC
      END
    ***
! -------------------------------------------
Running  FUNCTION
Channel  LONG
  CODE
  Channel = DDECLIENT(UPPER(COMMAND(0)))
  IF Channel <> 0
    DDEEXECUTE(Channel,'INFRONT')
    DDECLOSE(Channel)
  END
  RETURN(Channel)
! -------------------------------------------
Listen   PROCEDURE
x        LONG(0)
  CODE
  IF EVENT() = Event:ddeExecute
    If DDEVALUE() = 'INFRONT'
      x{prop:iconize} = 0
      BringWindowToTop(TARGET{prop:handle})
    END
  END
! -------------------------------------------

One interesting point about this member module is the fact that it's generic. In other words, because no program name is filled in the Member statement, it can be compiled in any program. However, because it's generic, all the functions it calls must be prototyped inside the module - hence the repeated map. Although we need to keep theInclude ('DDE.CLW') statement in the Global Map (because we're calling the Server part directly in the frame) I have also decided to keep the BringWindowToTop prototype there, as well. This means that the function will become available to your whole application in case you want to use it somewhere else.

And that's the whole shebang! Don't forget to register the template before using it! Enjoy...

As always I'm happy to answer questions about this - just email me at Bruce@capesoft.com.

Printer-friendly version

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links