Download source files - 14 Kb
Introduction
There are at least five Request for Enhancement's (RFE) in the JavaSoft bug database related to Mouse Wheel support in Java. One of the RFE's BugID #4202656 has 281 votes from developers requesting Sun for a fix. Sun has finally agreed to support this feature in JDK 1.4 codenamed Merlin accroding to the BugID #4289845 in its bug database.
This article is intended for those who don't want to wait till then or cannot move to a new JDK whenever Sun releases them. It shows how you can add support for Mouse Wheel with a little JNI code.
Searching for a solution
The first step was to develop a sample where this functionality can be tested. A sample from http://java.sun.com/docs/books/tutorial/uiswing/components/scrollpane.html#update looked ideal with a few modifications. The screenshot of this sample is shown below.

The next step was to use Spy++ to find out which window was actually getting WM_MOUSEWHEEL message and see if the message can be trapped. Spy++ showed only one Heavy weight window as all the Swing controls are light-weight implementations that share the DC of the parent frame. Also the Messages window confirmed that mouse wheel messages on any child Swing control was sent to this parent frame window. See the screen show which shows the window hierarchy and the messages.

Game Plan
Given the information above, implementation was kind of straight forward. To enable mouse wheel scrolling in a Swing widget, the WM_MOUSEWHEEL messages have to intercepted in the parent frame and somehow posted to the widget. Here's a simple plan to do this.
- Step 1: Get the
HWND of the parent JFrameEx.
- Step 2: Subclass the
HWND using SetWindowLong(hwnd, GWL_WNDPROC,(LONG)FrameWindowProc);
- Step 3: When
FrameWindowProc is called with a WM_MOUSEWHEEL, notify the java JFrameEx object.
- Step 4: In the
JFrameEx's notifyMouseWheel function, figure out which scrollpane should receive the message and call the setValue of it's vertical scrollbar
Step 1: This was easy using an FAQ on jguru. Here's the code snippet from JFrameEx.java public int getHWND()
{
DrawingSurfaceInfo drawingSurfaceInfo;
Win32DrawingSurface win32DrawingSurface;
int hwnd = 0;
drawingSurfaceInfo =
((DrawingSurface)(getPeer())).getDrawingSurfaceInfo();
if (null != drawingSurfaceInfo) {
drawingSurfaceInfo.lock();
win32DrawingSurface =
(Win32DrawingSurface)drawingSurfaceInfo.getSurface();
hwnd = win32DrawingSurface.getHWnd();
drawingSurfaceInfo.unlock();
}
return hwnd;
}
Step 2: Create a native entry point (using JNI) where we can call SetWindowLong. Here's the code snippet from MouseWheel.CPP.
JNIEXPORT void JNICALL Java_JFrameEx_setHook
(JNIEnv *pEnv, jobject f, jint hwnd)
{
jobject frame = pEnv->NewGlobalRef(f);
WNDPROC oldProc = (WNDPROC)::SetWindowLong((HWND)hwnd, GWL_WNDPROC, (LONG)FrameWindowProc);
setFrame((HWND)hwnd, frame);
setProc ((HWND)hwnd, oldProc);
}
Step 3: Here's the code snippet from MouseWheel.CPP which deals with the WM_MOUSEWHEEL message.
LRESULT CALLBACK FrameWindowProc(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
WNDPROC oldProc = getProc((HWND)hwnd);
if(uMsg == WM_MOUSEWHEEL)
{
MSG *pMsg = new MSG;
pMsg->hwnd = hwnd;
pMsg->message = uMsg;
pMsg->wParam = wParam;
pMsg->lParam = lParam;
_beginthread(StartNotifyThread, 0, pMsg);
return 0;
}
return ::CallWindowProc(oldProc, hwnd, uMsg, wParam, lParam);
}
void WINAPIV StartNotifyThread(LPVOID lpVoid)
{
MSG *pMsg = (MSG *)lpVoid;
jobject frame = getFrame(pMsg->hwnd);
WPARAM fwKeys = LOWORD(pMsg->wParam);
short zDelta = HIWORD(pMsg->wParam);
LPARAM xPos = GET_X_LPARAM(pMsg->lParam);
LPARAM yPos = GET_Y_LPARAM(pMsg->lParam);
notifyMouseWheel(frame,fwKeys,zDelta,xPos,yPos);
delete pMsg;
}
void notifyMouseWheel(jobject frame,short fwKeys,short zDelta,long xPos, long yPos)
{
HMODULE hMod = NULL;
JavaVM *vmBuf = NULL;
JNIEnv *pEnv = NULL;
jsize bufLen = 1;
jint nVMs = 0;
pJNI_GetCreatedJavaVMs pFunc = getJavaVM();
jint nRet = (*pFunc)(&vmBuf,bufLen,&nVMs);
vmBuf->AttachCurrentThread((void **)&pEnv,NULL);
jclass cls = pEnv->GetObjectClass(frame);
jmethodID mid = pEnv->GetMethodID(cls, "notifyMouseWheel", "(SSJJ)V");
if (mid == 0)
return;
pEnv->CallVoidMethod(frame, mid, (jshort)fwKeys, (jshort)zDelta, (jlong)xPos, (jlong)yPos);
vmBuf->DetachCurrentThread();
}
Step 4: Here's the code snippet from JFrameEx.java which is called from the previous step. The only trick here is that we need to get the scrollpane of the widget. From the documentation of JScrollPane we see that we have to call widget.getParent().getParent() to get the widget's scrollpane.
public void notifyMouseWheel(short fwKeys,short zDelta,long xPos, long yPos)
{
Point p = new Point((int)xPos,(int)yPos);
SwingUtilities.convertPointFromScreen(p,this);
Component c = SwingUtilities.getDeepestComponentAt(this, p.x, p.y);
try
{
JScrollPane scrollPane = (JScrollPane) c.getParent().getParent();
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
int nValue = scrollBar.getValue();
nValue = nValue + ((zDelta > 0) ? 5 : -5);
scrollBar.setValue(nValue);
} catch (Exception e){
}
}
That's it folks.
Notes
- There is a Build.bat in the zip file which can be use to build the sources. please edit this file to set the location of your VC++ and JDK 1.2.2 directories
- To run the code use "java ScrollDemo2"