为 Swing 部件添加鼠标滚轮支持






3.44/5 (14投票s)
2000年6月10日

168660

1450
本文展示了如何为 Java Swing 部件添加鼠标滚轮支持
引言
JavaSoft的错误数据库中至少有五个关于Java中鼠标滚轮支持的增强请求(RFE)。其中一个RFE,BugID #4202656,有来自开发者的281个投票,请求Sun进行修复。根据BugID #4289845中的信息,Sun最终同意在代号为Merlin的JDK 1.4中支持此功能。
本文面向那些不想等到那个时候或者无法在新JDK发布时迁移到新JDK的人。它展示了如何使用一点JNI代码来添加对鼠标滚轮的支持。
寻找解决方案
第一步是开发一个可以测试此功能的示例。从这里的示例稍作修改后,看起来很理想。该示例的屏幕截图如下所示
下一步是使用Spy++来找出哪个窗口实际接收到WM_MOUSEWHEEL
消息,并查看该消息是否可以被捕获。Spy++仅显示一个重量级窗口,因为所有Swing控件都是轻量级实现,共享父框架的DC。而且,消息窗口确认了在任何子Swing控件上的鼠标滚轮消息都发送到此父框架窗口。请参阅屏幕截图,其中显示了窗口层次结构和消息。
计划
根据以上信息,实现方法非常直接。要在Swing小部件中启用鼠标滚轮滚动,必须在父框架中截获WM_MOUSEWHEEL
消息,并以某种方式将其发布到小部件。以下是一个简单的计划来完成此操作
- 步骤 1:获取父
JFrameEx
的HWND
。 - 步骤 2:使用
SetWindowLong(hwnd, GWL_WNDPROC,(LONG)FrameWindowProc);
对HWND
进行子类化 - 步骤 3:当
FrameWindowProc
使用WM_MOUSEWHEEL
被调用时,通知JavaJFrameEx
对象。 - 步骤 4:在
JFrameEx
的notifyMouseWheel
函数中,确定哪个滚动窗格应该接收消息,并调用其垂直滚动条的setValue
步骤 1
这使用jguru上的一个FAQ很容易实现。这是来自JFrameEx.java的代码片段。
// Returns the HWND for canvas.
// This is undocumented, but works on JDK1.1.8, JDK1.2.2 and JDK1.3
public int getHWND()
{
DrawingSurfaceInfo drawingSurfaceInfo;
Win32DrawingSurface win32DrawingSurface;
int hwnd = 0;
// Get the drawing surface
drawingSurfaceInfo =
((DrawingSurface)(getPeer())).getDrawingSurfaceInfo();
if (null != drawingSurfaceInfo) {
drawingSurfaceInfo.lock();
// Get the Win32 specific information
win32DrawingSurface =
(Win32DrawingSurface)drawingSurfaceInfo.getSurface();
hwnd = win32DrawingSurface.getHWnd();
drawingSurfaceInfo.unlock();
}
return hwnd;
}
第二步
创建一个本机入口点(使用JNI),我们可以在其中调用SetWindowLong
。这是来自MouseWheel.CPP的代码片段。
// JNI native entry point for subclassing the window
JNIEXPORT void JNICALL Java_JFrameEx_setHook
(JNIEnv *pEnv, jobject f, jint hwnd)
{
// ensure that the Java object can be called from any thread.
jobject frame = pEnv->NewGlobalRef(f);
WNDPROC oldProc = (WNDPROC)::SetWindowLong((HWND)hwnd, GWL_WNDPROC, (LONG)FrameWindowProc);
// store the Java object
setFrame((HWND)hwnd, frame);
// store the old window proc
setProc ((HWND)hwnd, oldProc);
}
步骤 3
这是来自MouseWheel.CPP的代码片段,它处理WM_MOUSEWHEEL
消息。
// Window Proc for subclassing.
LRESULT CALLBACK FrameWindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
)
{
// Get the WindowProc for this window.
WNDPROC oldProc = getProc((HWND)hwnd);
// When we get a mouse wheel message
if(uMsg == WM_MOUSEWHEEL)
{
MSG *pMsg = new MSG;
pMsg->hwnd = hwnd;
pMsg->message = uMsg;
pMsg->wParam = wParam;
pMsg->lParam = lParam;
// send it to the Java code from a new thread.
_beginthread(StartNotifyThread, 0, pMsg);
return 0;
}
return ::CallWindowProc(oldProc, hwnd, uMsg, wParam, lParam);
}
// Helper Thread for notification.
void WINAPIV StartNotifyThread(LPVOID lpVoid)
{
MSG *pMsg = (MSG *)lpVoid;
// This is the Java object that needs to be notified.
jobject frame = getFrame(pMsg->hwnd);
// extract info from the wparam and lparam.
WPARAM fwKeys = LOWORD(pMsg->wParam);
short zDelta = HIWORD(pMsg->wParam);
LPARAM xPos = GET_X_LPARAM(pMsg->lParam);
LPARAM yPos = GET_Y_LPARAM(pMsg->lParam);
// call the helper function.
notifyMouseWheel(frame,fwKeys,zDelta,xPos,yPos);
delete pMsg;
}
// This helper is the one that actually invokes the Java JFrameEx's notifyMouseWheel method
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;
// Get the Java VM.
pJNI_GetCreatedJavaVMs pFunc = getJavaVM();
jint nRet = (*pFunc)(&vmBuf,bufLen,&nVMs);
// Attach this thread.
vmBuf->AttachCurrentThread((void **)&pEnv,NULL);
// Inform the Java object that the child has been created.
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);
// Detach this thread.
vmBuf->DetachCurrentThread();
}
步骤 4
这是来自JFrameEx.java的代码片段,它从上一步调用。这里的唯一技巧是我们需要获取小部件的滚动窗格。从JScrollPane
的文档中,我们看到我们需要调用widget.getParent().getParent()
来获取小部件的滚动窗格。
// this is the function which serves as a call back when
// a mouse wheel movement is detected.
public void notifyMouseWheel(short fwKeys,short zDelta,long xPos, long yPos)
{
// Convert screen coordinates to component specific offsets.
Point p = new Point((int)xPos,(int)yPos);
SwingUtilities.convertPointFromScreen(p,this);
// Find the embedded Swing component which should receive the scroll messages
Component c = SwingUtilities.getDeepestComponentAt(this, p.x, p.y);
try
{
// Get the scroll pane for the widget.
JScrollPane scrollPane = (JScrollPane) c.getParent().getParent();
// Get the vertical scrollbar for the scroll pane.
JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
// Get the current value and set the new value depending on
// the direction of the mouse wheel.
int nValue = scrollBar.getValue();
nValue = nValue + ((zDelta > 0) ? 5 : -5);
scrollBar.setValue(nValue);
} catch (Exception e){
// Ignore exceptions.
}
}
就是这样,伙计们!
注释
- zip文件中有一个Build.bat,可用于构建源代码。请编辑此文件以设置您的VC++和JDK 1.2.2目录的位置。
- 要运行代码,请使用“
java ScrollDemo2
”。
历史
- 2000年7月3日:初始版本
许可证
本文没有附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论板与作者联系。可以在这里找到作者可能使用的许可证列表。