65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.44/5 (14投票s)

2000年6月10日

viewsIcon

168660

downloadIcon

1450

本文展示了如何为 Java Swing 部件添加鼠标滚轮支持

引言

JavaSoft的错误数据库中至少有五个关于Java中鼠标滚轮支持的增强请求(RFE)。其中一个RFE,BugID #4202656,有来自开发者的281个投票,请求Sun进行修复。根据BugID #4289845中的信息,Sun最终同意在代号为Merlin的JDK 1.4中支持此功能。

本文面向那些不想等到那个时候或者无法在新JDK发布时迁移到新JDK的人。它展示了如何使用一点JNI代码来添加对鼠标滚轮的支持。

寻找解决方案

第一步是开发一个可以测试此功能的示例。从这里的示例稍作修改后,看起来很理想。该示例的屏幕截图如下所示

Sample Image - mousewheel.gif

下一步是使用Spy++来找出哪个窗口实际接收到WM_MOUSEWHEEL消息,并查看该消息是否可以被捕获。Spy++仅显示一个重量级窗口,因为所有Swing控件都是轻量级实现,共享父框架的DC。而且,消息窗口确认了在任何子Swing控件上的鼠标滚轮消息都发送到此父框架窗口。请参阅屏幕截图,其中显示了窗口层次结构和消息。

Sample Image - spy.gif

计划

根据以上信息,实现方法非常直接。要在Swing小部件中启用鼠标滚轮滚动,必须在父框架中截获WM_MOUSEWHEEL消息,并以某种方式将其发布到小部件。以下是一个简单的计划来完成此操作

  • 步骤 1:获取父JFrameExHWND
  • 步骤 2:使用SetWindowLong(hwnd, GWL_WNDPROC,(LONG)FrameWindowProc);HWND进行子类化
  • 步骤 3:当FrameWindowProc使用WM_MOUSEWHEEL被调用时,通知Java JFrameEx对象。
  • 步骤 4:在JFrameExnotifyMouseWheel函数中,确定哪个滚动窗格应该接收消息,并调用其垂直滚动条的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日:初始版本

许可证

本文没有附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论板与作者联系。可以在这里找到作者可能使用的许可证列表。

© . All rights reserved.