By Brian Johnson
The Windows Shell API contains a fairly large number of functions and interfaces that allow a programmer to interact fully with the Windows shell. Among the most common functions associated with shell operations are drag-and-drop operations, file-manager extensions, file viewing, and file execution. This article looks at the file-execution aspect of the shell library using Microsoft Visual J++ 6.0 (VJ6). We’ll access the shell functionality from VJ6 using J/Direct, and wrap a couple of these functions in a WFC control that you can modify and use as you wish.
The APIsThe functions we’re going to use in this article are part of the Windows Shell API; specifically, we’ll cover those from Shell32.dll: ShellExecute, FindExecutable, and ExtractIcon.
I’d like to give you a taste of what these shell functions can do. To really get down and dirty with the Windows shell, look through the Windows Shell API documentation in the MSDN Library (available from Microsoft on CD, or at the MSDN Web site: http://msdn.microsoft.com/developer). Also keep in mind that the shell functionality is enhanced in Windows 98, and on machines using Microsoft Internet Explorer 4.0 and greater. Full details of the differences are listed in the Microsoft Internet SDK. We’ll keep it generic for this article. Once you latch on to what’s available here, you’ll definitely feel the urge to dig into that enhanced functionality.
To get started, we’ll discuss the purpose and form of each of the previously mentioned functions. Then we’ll cover the use of J/Direct to access this functionality.
ShellExecuteShellExecute takes a file name and performs an “open,” “print,” or “explore” operation on it. This function is fairly automatic, with Windows deciding what application or printer to use when the name of a data file is passed. The Windows Explorer is used in the case of a folder name or URL. The ShellExecute function takes as arguments the items listed in Figure 1.
Parameter | Description |
hWnd | Handle to your application’s window. |
lpOperation | One of “open,” “print,” or “explore.” |
lpFile | Name of the file to execute. |
lpParameters | Parameters passed to the application being executed. |
lpDirectory | String containing the default directory for the operation. |
nShowCmd | Constant values passed that specify how the application is launched (see Figure 2). |
Figure 1: Parameters for the ShellExecute function.
The constants for nShowCmd are described in Figure 2. Most are self explanatory. The exception is the SW_SHOWDEFAULT flag, which bases its show state on a flag in the STARTUPINFO structure (which is outside the scope if this article).
Constant | Description |
SW_HIDE | Hides the calling window while opening the new one. |
SW_MAXIMIZE | Maximizes the target window. |
SW_MINIMIZE | Minimizes the target window. |
SW_RESTORE | Restores the target to its previous state. |
SW_SHOW | Activates the open window in current position. |
SW_SHOWDEFAULT | Show state is based on STARTUPINFO structure. |
SW_SHOWMAXIMIZED | Opens the window maximized. |
SW_SHOWMINIMIZED | Opens the window minimized. |
SW_SHOWMINNOACTIVE | Opens the window to the most recent state without focus. |
SW_SHOWNA | Opens the window without focus. |
SW_SHOWNORMAL | Opens or restores a window to it’s most recent state. |
Figure 2: nShowCommand constants.
FindExecutableA potentially important piece of information for anyone writing an application that opens a file is the name of the executable associated with the file. Using the FindExecutable function, you can determine the name of the associated application. This function takes the name of the file and the directory as arguments, along with a string buffer used to hold the name of the executable. If the function is successful, it returns a value greater than 32. The parameters passed to the function are listed in Figure 3. We’ll use this function to get the name of an executable we can pass to the ExtractIcon function.
Parameter | Description |
lpFile | Pointer to the string containing the file name. |
lpDirectory | Pointer to the string containing the directory path. |
lpResult | Pointer to the string (buffer) to hold the result. |
Figure 3: Parameters for the FindExecutable function.
ExtractIconExtractIcon is another simple function that allows you to get an icon from a Windows executable. Using this function, we can get an icon to display that represents the application we’re launching. ExtractIcon takes three parameters (see Figure 4), and returns a handle to the extracted icon.
Parameter | Description |
hInst | Instance handle. |
lpszExeFileName | Pointer to string containing the file name. |
nIconIndex | Index number of the icon desired. |
Figure 4: Parameters for the ExtractIcon function.
Now that we’ve reviewed the basic functions we want to use, let’s look at how to use these functions in VJ6 using J/Direct.
Converting Function Calls to Java MethodsIt’s axiomatic that we want to create classes we can reuse easily. Sometimes it makes sense to turn a class into a full-fledged component. In this case it will be fun to take the functionality available from these API calls and create a button to launch a file.
To get started, we’ll use J/Direct Call Builder to create a class that declares these functions. If you haven’t used the J/Direct Call Builder before, simply select View | Other Windows | J/Direct Call Builder from the VJ6 menu (see Figure 5).
Figure 5: The J/Direct Call Builder dialog box.
Using this utility is easy; simply type the name of the Win32 function you want to use in the Find text box. You can resize the bottom window in the J/Direct Call Builder to see the dll.import statement associated with the function. You can either copy this function to your target file using the Copy To Target button, or you can simply cut and paste the function into your class. In this example, I let J/Direct Call Builder create its default Win32 class in my project. (The example solution for this article is available for download; see end of article for details.)
The import statement for the ShellExecute function looks like this:
/**
* @dll.import("SHELL32", auto)
*/
public static native int ShellExecute(int hwnd,
String lpOperation, String lpFile, String lpParameters,
String lpDirectory, int nShowCmd);
Note that the arguments for this function correspond to the functions listed in Figure 1. You can use the J/Direct Call Builder to retrieve the constants listed in Figure 2. You’ll need to retrieve these constants individually, but you can make multiple simultaneous selections within the J/Direct Call Builder to have it generate all of these declarations at once (see Figure 6).
public static final int SW_HIDE = 0;
public static final int SW_MAXIMIZE = 3;
public static final int SW_MINIMIZE = 6;
public static final int SW_NORMAL = 1;
public static final int SW_RESTORE = 9;
public static final int SW_SHOW = 5;
public static final int SW_SHOWDEFAULT = 10;
public static final int SW_SHOWMAXIMIZED = 3;
public static final int SW_SHOWMINIMIZED = 2;
public static final int SW_SHOWMINNOACTIVE = 7;
public static final int SW_SHOWNA = 8;
public static final int SW_SHOWNOACTIVATE = 4;
public static final int SW_SHOWNORMAL = 1;
Figure 6: Declaration code for the nShowCommand constants shown in Figure 2.
The method declarations for FindExecutable and ExtractIcon are added in the same way as the ShellExecute function. The declaration for the FindExecutable function is:
/**
@dll.import("SHELL32", auto)
*/
public static native int FindExecutable(String lpFile,
String lpDirectory, StringBuffer lpResult);
And the declaration for the ExtractIcon function comes out this way:
/**
@dll.import("SHELL32", auto)
*/
public static native int ExtractIcon(int hInst,
String lpszExeFileName, int nIconIndex);
With these shell functions and constants set up, let’s take a look at how they might be used in a very simple file-launching application.
Building the ControlAs mentioned earlier, there are a number of different ways you can add these J/Direct call functions to your application. I created a control that uses the shell functions we’ve discussed. The idea for this control came from some of the snazzy controls you used to be able to get for Visual Basic. One of the cool controls I used to like was a single button that contained an icon and caption.
To get things rolling, let’s create a new control project, by selecting File | New Project to open the New Project dialog box. Choose the Control project type from the Visual J++ Projects | Components folder on the New page of the dialog box. This will give us a project with a canvas on which we can add WFC components. To make this all work the way we want, we need to rename the Java source file in the Project Explorer window. Then we need to change the name of the class in the source file to the same name as the source file. In this example, the name of the control file is LaunchButton.java, so the class declaration should look like this:
public class LaunchButton extends UserControl
This project uses a single button on a ToolBar control as its base. Figure 7 shows the canvas with the ToolBar control added. (When you create a control this way, you want to be able to fine tune the placement of the controls on your canvas; I usually uncheck the Snap to Grid selection in the Visual J++ options dialog box.) I placed the ToolBar control in the upper-left corner of the canvas. The button size is set to something that makes sense to start out with; in this case it’s 75 by 60 pixels. The divider property should be set to false to hide the extra bar that would exist at the top of the control. I added an image list to hold the default icon at index 0. The icon retrieved from the executable will be held at index 1. I then added an ImageList control to the control, and set the icon size for the button to 32 by 32 pixels. This ImageList should be set as the ImageList for the ToolBar in the property editor.
Figure 7: Addition of the ToolBar control.
The FileLaunch control has two custom properties. You can view them in the WFC Component Builder dialog box (see Figure 8). The launchFile property is the name and path of the file that will be launched when the button is pressed. The caption property places a caption under the icon on the button. To add custom properties to a new control, open the WFC Component Builder by right clicking on the LaunchButton class in the Class Outline window. To keep things as simple as possible, make these two properties strings. If you were making a control like this that you wanted to release, you might consider using a custom property editor to make it easier to select the file you want to launch.
Figure 8: The WFC Component Builder displaying the FileLaunch control’s two custom properties.
In the Add WFC Property dialog box, enter java.lang.string in the Data Type text box and enter Data in the Category text box for each of these properties (see Figure 9). You can also add a description that will become a comment for the member variables associated with the new properties.
Figure 9: The Add WFC Property dialog box.
With everything in place we can now add some functionality to the new control. Choose a default icon for the toolbar through the images property of the ImageList control, and add an icon to the top of the list. This will be the icon that shows up when an icon is first used, or if no valid icon is retrieved using the ExtractIcon method. I selected a happy face from the collection of icons that ships with Visual Studio (again, see Figure 7). To make the icon the default for the new control, the following line is added to the constructor for the LaunchButton class:
toolBarButton1.setImageIndex(0);
The last housekeeping chore that must be done is to ensure that the button is resized when the canvas is resized. To do this, add the following code to the resize event for the LaunchButton class:
private void LaunchButtonTest_resize(
Object source, Event e)
{
Point myPoint;
myPoint = this.getSize();
myPoint.x = myPoint.x - 5;
myPoint.y = myPoint.y - 5;
toolBar1.setSize(this.getSize());
toolBar1.setButtonSize(myPoint);
}
This event deserves a little explanation. The ToolBar control has two properties of which you need to be aware. First, the ToolBar control has a size property of its own. This is set to the same size as the canvas using the setSize method. The first button in the ToolBar becomes the button in our control. To make sure the edges of the button aren’t cut off by either the canvas or the ToolBar itself, the x and y points for the button are set to five pixels shorter than the current value of the canvas. By doing this, you can be sure that the button will behave properly when it’s used as a control.
To set the caption for the button, add the following statement to the setCaption method:
toolBarButton1.setText(m_Caption);
Now we can get to the heart of the control. You can follow along in Listing One. The setLaunchFile method does three things. First, it sets the name of the file that will be launched when the button is clicked. This method takes the name of the file to be launched, and checks to see if there is an executable associated with the file listed using the FindExecutable method. If there is, its path and file name are put into the string buffer, bufExeName. (If m_LaunchFile is already an executable file, it will be reflected in bufExeName). The return value of the method is greater than 32 if the FindExecutable function succeeds. If not, we’ll launch an error message and return from the setLaunchFile method.
Next, the getLaunchFile method uses the ExtractIcon method to retrieve the icon that will be displayed on the button. The handle to the icon should be anything above 0, so that’s checked before trying to change the icon of the file. There are a number of ways you can change the icon on the button. What I finally decided to do was to create an Icon[] array, place the extracted icon at index 0, and then call the setImageIndex method for toolBarButton1 to change the icon that was displayed.
The final important event in the control is toolBar1_buttonClick. After making sure that m_LaunchFile isn’t an empty string, I simply called the ShellExecute method with some default parameters to launch m_LaunchFile using the “open” argument. The final statement looks like this:
Win32.ShellExecute(this.getHandle(),”open”, m_LaunchFile,
null, null, Win32.SW_SHOWNORMAL);
If you intend to use this control as the basis of a control that you want released publicly, you’ll want to add some proper error checking to this event and give your users control over some of the other arguments to the ShellExecute method. This is easy to do using the J/Direct Call Builder. Finally, be aware that this function doesn’t handle the creation of new processes very well, so you’ll want to delve into the use of ShellExecuteEx — another shell API call — to exercise greater control over this type of launch. An example might be the creation of an instance of Command.com.
If everything comes together you’ll be able to build the control by selecting Build | Build. To test the control, you’ll need to add a form to your control, and add the control to the WFC Controls Toolbox using the Customize Toolbox dialog box. This dialog box can be accessed through the Tools | Customize Toolbox menu item, or by right-clicking in the Toolbox. Drop the new control onto the form you created, set the caption and launchFile properties, and you’re done. Your test form should look something like Figure 10.
Figure 10: A successfully implemented test form.
One last remark about editing control code: When you change the code in the control during debugging, you’ll need to delete any instance of the control that you placed on the test form. Close the test form, then rebuild the control after making your changes. Finally, re-open the form and drop a new instance of the control onto it. Your changes will appear immediately.
ConclusionThis article only scratches the surface of what shell functions can add to your WFC applications. By wrapping these functions in a custom control, you can make it easy to use these functions in a specific manner. If you haven’t put a control together this way before, try it. It’s a lot of fun to use a powerful API like the Windows Shell API to take command with your Java applications.
Brian Johnson is a freelance writer and programmer in Orlando, FL. You can reach him at brianjay@gate.net, or check out his Web site at http://brianj.net.
Listing One — LaunchButton.javaimport com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
import com.ms.lang.Delegate;
/**
* This class is a visual component. The entry point for
* class execution is the constructor.
*
* This class can be used as an ActiveX control. Check the
* check box for this class on the Project Properties COM
* Classes tab, or remove // from the next line:
// * @com.register(
clsid=9D15E1A0-B12C-11D2-9949-30D249C10000,
typelib=9D15E1A1-B12C-11D2-9949-30D249C10000)
*/
public class LaunchButton extends UserControl
{
// Caption of the button.
private String m_Caption;
// Name/Path of the file to be launched.
private String m_LaunchFile;
public LaunchButton()
{
// Required for Visual J++ Forms Designer support.
initForm();
this.setCaption("LaunchButton");
// TODO: Add any constructor code after initForm call.
}
// Caption of the button.
public String getCaption()
{
// TODO: Add your own implementation.
return m_Caption;
}
// Name of the file to be launched.
public String getLaunchFile()
{
// TODO: Add your own implementation.
return m_LaunchFile;
}
// Caption of the button.
public void setCaption(String value)
{
// TODO: Add your own implementation.
m_Caption = value;
toolBarButton1.setText(m_Caption);
}
// Name of the file to be launched.
public void setLaunchFile(String value)
{
// Add a few of variables to work with.
StringBuffer bufExeName = new StringBuffer();
String exeName;
int iReturn;
int hIcon;
Icon myIcon;
// Create an array of Image to hold the new icon.
Image[] myImages = new Image[1];
m_LaunchFile = value;
// Make sure buffer is large enough for the file path.
bufExeName.ensureCapacity(256);
// Check to see if FindExecutable returns a valid exe.
iReturn = Win32.FindExecutable(m_LaunchFile, null, bufExeName);
if (iReturn <= 32)
{
MessageBox.show(
"No launchable executable file found. " +
"Check your file name and path.", "Error");
m_LaunchFile = ";
return;
}
exeName = bufExeName.toString();
hIcon = Win32.ExtractIcon(this.getHandle(),exeName,0);
if (hIcon > 0)
{
Icon nIcon = new Icon(hIcon);
myImages[0] = nIcon;
imageList1.setImages(myImages);
toolBarButton1.setImageIndex(0);
this.invalidate();
}
}
private void toolBar1_buttonClick(Object source,
ToolBarButtonClickEvent e)
{
// If there is no file name, return from the method.
if (m_LaunchFile == ") return;
// Execute the file.
Win32.ShellExecute(this.getHandle(), "open",
m_LaunchFile, null, null, Win32.SW_SHOWNORMAL);
}
private void LaunchButton_resize(Object source, Event e)
{
// Set up the control to resize itself properly.
Point myPoint;
myPoint = this.getSize();
myPoint.x = myPoint.x - 5;
myPoint.y = myPoint.y - 5;
toolBar1.setSize(this.getSize());
toolBar1.setButtonSize(myPoint);
}
/**
* NOTE: The following code is required by the Visual J++
* Forms Designer. It can be modified using the form
* editor. Do not modify it using the code editor.
*/
Container components = new Container();
ToolBar toolBar1 = new ToolBar();
ToolBarButton toolBarButton1 = new ToolBarButton();
ImageList imageList1 = new ImageList();
private void initForm()
{
// NOTE: This form is storing resource information in
// an external file. Do not modify the string parameter
// to any resources.getObject() function call. For
// example, do not modify "foo1_location" in the
// following line of code, even if the name of the Foo
// object changes:
// foo1.setLocation((Point)resources.getObject(
// "foo1_location"));
IResourceManager resources =
new ResourceManager(this, "LaunchButton");
this.setSize(new Point(78, 65));
this.setText("Control1");
this.addOnResize(
new EventHandler(this.LaunchButton_resize));
toolBarButton1.setImageIndex(0);
toolBarButton1.setText("LaunchButton");
imageList1.setImageSize(new Point(32, 32));
imageList1.setImageStream(
(ImageListStreamer)resources.getObject(
"imageList1_imageStream"));
/* @designTimeOnly imageList1.setLocation(
new Point(10, 70)); */
toolBar1.setSize(new Point(78, 62));
toolBar1.setTabIndex(0);
toolBar1.setButtons(
new ToolBarButton[] {toolBarButton1});
toolBar1.setButtonSize(new Point(75, 60));
toolBar1.setDivider(false);
toolBar1.setDropDownArrows(true);
toolBar1.setImageList(imageList1);
toolBar1.setShowToolTips(true);
toolBar1.addOnButtonClick(
new ToolBarButtonClickEventHandler(
this.toolBar1_buttonClick));
this.setNewControls(new Control[] {toolBar1});
}
// NOTE: End of form designer support code.
public static class ClassInfo
extends UserControl.ClassInfo
{
// TODO: Add your property and event info here.
public static final PropertyInfo caption =
new PropertyInfo(LaunchButton.class, "caption",
String.class, CategoryAttribute.Data,
new DescriptionAttribute("Caption of the button."));
public static final PropertyInfo launchFile =
new PropertyInfo(LaunchButton.class, "launchFile",
String.class, CategoryAttribute.Data,
new DescriptionAttribute(
"Name of the file to be launched."));
public void getEvents(IEvents events)
{
super.getEvents(events);
}
public void getProperties(IProperties props)
{
super.getProperties(props);
props.add(launchFile);
props.add(caption);
}
}
}
End Listing One