|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 7 of 7
The rest of the work is really straightforward Win32 programming. We register our window class:
// Register window class
WNDCLASSEX l_Class;
l_Class.cbSize = sizeof( l_Class );
l_Class.style = 0;
l_Class.lpszClassName = TEXT( "DesktopIndicatorHandlerClass" );
l_Class.lpfnWndProc = WndProc;
l_Class.hbrBackground = NULL;
l_Class.hCursor = NULL;
l_Class.hIcon = NULL;
l_Class.hIconSm = NULL;
l_Class.lpszMenuName = NULL;
l_Class.cbClsExtra = 0;
l_Class.cbWndExtra = 0;
if( !RegisterClassEx( &l_Class ) )
return;
We then create our invisible window (there's one for each handler instance):
// Create window
m_window = CreateWindow
(
TEXT( "DesktopIndicatorHandlerClass" ),
TEXT( "DesktopIndicatorHandler" ),
WS_POPUP,
0, 0, 0, 0,
NULL,
NULL,
0,
NULL
);
if( !m_window )
return;
// Set this pointer
SetWindowLong( m_window, GWL_USERDATA, (LONG) this );
Note that we store our this pointer in the user area on the window. We do this in order to solve the same problem we had with the thread procedure --
our window procedure is also a static function.
We can now -- finally! -- create our taskbar icon:
// Add shell icon NOTIFYICONDATA m_iconData; m_iconData.cbSize = sizeof( m_iconData ); m_iconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; m_iconData.uCallbackMessage = WM_DESKTOPINDICATOR_CLICK; m_iconData.uID = 0; m_iconData.hWnd = m_window; m_iconData.hIcon = m_icon; strcpy( m_iconData.szTip, m_tooltip ); Shell_NotifyIcon( NIM_ADD, &m_iconData );
We are using our own user-defined message for the callback, which is different from the message for the thread procedure, because they are both interpreted in the same place.
Our window procedure is simple:
LRESULT CALLBACK DesktopIndicatorHandler::WndProc( HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam )
{
// Check for our special notification message
if( ( uMessage == WM_DESKTOPINDICATOR_CLICK ) && ( INT(lParam) == WM_LBUTTONDOWN ) )
{
DesktopIndicatorHandler *l_this = (DesktopIndicatorHandler *) GetWindowLong( hWnd, GWL_USERDATA );
// Click!
l_this->fireClicked();
return 0;
}
else
return DefWindowProc( hWnd, uMessage, wParam, lParam );
}
It extracts the this pointer and fires a click. The fireClicked method delegates upward to the Java wrapper instance via JNI:
void DesktopIndicatorHandler::fireClicked()
{
g_DesktopIndicatorThread.m_env->CallVoidMethod( m_object, m_fireClicked );
}
Note that we are doing the call through the thread's environment, because that is the thread context in which we are running.
If you remember your JNI, you know that nonstatic native methods are called with a jobject parameter. But how did we match the jobject, which is a Java instance, with our C++ handler instance? Cast your mind back to that handler field in the wrapper ... I'm sure it's all coming back to you now, right? Here's the static C++ method to extract our pointer
from the Java object:
DesktopIndicatorHandler *DesktopIndicatorHandler::extract( JNIEnv *env, jobject object )
{
// Get field ID
jfieldID l_handlerId = env->GetFieldID( env->GetObjectClass( object ), "handler", "I" );
// Get field
DesktopIndicatorHandler *l_handler = (DesktopIndicatorHandler *) env->GetIntField( object, l_handlerId );
return l_handler;
}
You may feel apprehensive about storing C++ pointers in Java instances. Don't worry; Java will not garbage-collect your C++ objects. This is, in fact, a standard technique for making objects opaque. As far as Java is concerned, the pointer is just a number stored in a field. In this case, Java makes no use of this number, and without knowing what it means, it's just data. In some cases, these numbers may be stored in Java and returned to native code by other native method calls. In such cases, the number is called a handle.
There's one last trick concerning this: we're storing the jobject in our handler instance, and using it to call the fireClicked method. This is dangerous, because it may be readily garbage-collected, in which case terrible things will happen if we reference
it. It's easy to forget this, because we never worry about reference counting in Java. But we're not in Java, and must make
sure that a reference is counted for us:
// Reference object m_object = env->NewGlobalRef( object );
We also must be careful to release the reference when it's no longer needed, or else the Java instance will never be garbage-collected:
// Release our reference g_DesktopIndicatorThread.m_env->DeleteGlobalRef( m_object );
The full source code is available online. See Resources for the URL.
Here is a small Java application to test the feature. Make sure the DLL and the ICO files are in your path:
public class DesktopIndicatorTest implements DesktopIndicatorListener
{
static DesktopIndicatorTest listener;
static int clicks = 4;
static int quickImage;
static int comicImage;
static public void main( String args[] )
{
// Initialize JNI extension
if( !DesktopIndicator.initialize() )
{
System.err.println( "Either you are not on Windows, or there is a problem with the DesktopIndicator library!" );
return;
}
// Load quick image
quickImage = DesktopIndicator.loadImage( "quick.ico" );
if( quickImage == -1 )
{
System.err.println( "Could not load the image file \"quick.ico\"!" );
return;
}
// Load comic image
comicImage = DesktopIndicator.loadImage( "comic.ico" );
if( comicImage == -1 )
{
System.err.println( "Could not load the image file \"comic.ico\"!" );
return;
}
// Create the indicator
DesktopIndicator indicator = new DesktopIndicator( quickImage, "Quick! Click me!" );
listener = new DesktopIndicatorTest();
indicator.addDesktopIndicatorListener( listener );
indicator.show();
// Instructions
System.err.println( "See the taskbar icon? Click it!" );
// Wait for the bitter end
try
{
synchronized( listener )
{
listener.wait();
}
}
catch( InterruptedException x )
{
}
// Time to die
indicator.removeDesktopIndicatorListener( listener );
indicator.hide();
System.err.println( "Goodbye!" );
}
public void onDesktopIndicatorClicked( DesktopIndicator source )
{
System.err.println( String.valueOf( clicks ) + " click(s) left..." );
// Countdown to death
clicks--;
if( clicks % 2 == 1 )
{
// Comic!
source.update( comicImage, "A message for you, sir!" );
}
else
{
// Quick!
source.update( quickImage, "Quick! Click me!" );
}
if( clicks == 0 )
{
// The end is nigh
synchronized( listener )
{
listener.notifyAll();
}
}
}
}
(This is a very bad application, because it does not offer the same functionality on other platforms. It does, however, fail rather gracefully.)
The desktop indicator solution is certainly usable as is, but may be extended to offer even more functionality.
JNI does have a serious limitation when it comes to extending Java's GUI: there is no standard way for getting native window handles from AWT components. Each VM may implement its AWT differently, and store the window handle in a different place. Future versions of the VM may handle windows in still different ways, ways that cannot be predicted with any certainty right now. With Java 2 platform VMs just starting to get a foothold (without this feature), I don't expect to see a standard way for doing this anytime soon.
This limitation means that it is quite impossible for your JNI extensions to alter or otherwise work with AWT-created components. You can definitely create your own mini-AWT framework to handle your platform-specific windowing features, but this requires considerable work, never integrates well, and is, of course, nonportable.
While I've focused this article on a GUI extension, there are other platform-specific features that users may expect that are not part of the GUI. One example is the Windows' registry. When running on Windows, Java applications cannot normally write their settings to the registry, and, as such, they will not be saved on the user's profile. A class that uses JNI to write to the registry on Windows, and writes to a file on other platforms, gives you the best of all worlds, without compromising its persistence features. I have supplied a rather primitive solution for this on my JNI site (see Resources).
I am firmly convinced that JNI extensions are not merely cool toys for Java, but are a real necessity if Java is to transcend the small world of applets and compete in the desktop-application arena. Java aficionados have hitherto downplayed JNI for an obvious reason: applications which rely on JNI features will no longer be portable, and Java will lose its main edge and become just another VM architecture. On the other hand, with careful design considerations, JNI extensions may save the day.
I am maintaining a small site for supporting public-domain open source JNI extensions similar to the one described in this article. If more people contribute, we can build a useful library of application boosters and make Java a viable platform for real-world, cross-platform applications. See Resources for the URL.
True native behavior is not the only problem with Java applications. Deployment is another big issue. Of course, you can deploy a Java application just like any other application, and there are several commercial kits for cross-platform installation, many running in Java. But it's still a far cry from deploying Java applets, and millions of miles away from HTML/DHTML Web applications, where all you have to do is turn on your Web server.
Wouldn't it be great if you could just give your users a URL for your applications and have them point their VMs to it? It would be just like using a browser, but with all the benefits of a real application with real windowing. The initiative is under way, and you are welcome to join and help where you can. It's all free software under the GNU General Public License. See Resources for more details.
Read more about Core Java in JavaWorld's Core Java section.