为啥鼠标左键按一下有时会变成双击了?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为啥鼠标左键按一下有时会变成双击了?相关的知识,希望对你有一定的参考价值。

明明只听到按了一下的声音,却变成双击了。

鼠标使用一段时间之后由于微动开关的磨损,就会导致接触不良,所以经常会发生连击现象,如果你的鼠标不是特别贵可以直接考虑换鼠标,如果价格很高可以考虑单独更换微动开关。

解决鼠标左键点击变双击的方法

1、 工具准备好后,将鼠标打开!大卸八块之后,将鼠标的线拔掉,碍事!

2、找到左轮按键。

3、撬开左键上的红色按钮.注意不要把左键冒弄丢了。

4、前开铜片.注意不要把铜片弄断了,否则就没有弹性了。

5、看到铜片下面有黑色的粉末,用挫布小心弄干净。

6、然后在组装起来就行了!



参考技术A 鼠标没有问题的话,应该是设置问题,我的电脑被重装了一次后,鼠标就出现同样的问题,并且换了鼠标也是同样的。后来在控制面板把鼠标的双击速度调慢了点,就没有这种情况了。 参考技术B 把鼠标插在别的机子上看看,是不是鼠标的问题。如果不是硬件问题的话,打开控制面板里的鼠标选项,如果没有启用单击锁定,试试调整双击速度 参考技术C 鼠标可能因为经常用所以会变的很敏感,所以,有时候你按下去的时候,里面的键并不能立刻弹回来,就会变成双击,而且,最好看一下你鼠标配置,看是不是有人把你的鼠标设置成了单击状态!!! 参考技术D 鼠标坏了~如果不换的话~那就把双击速度调至最快~这样相对来说会好一点~

win32day05-鼠标消息/定时器消息/菜单

鼠标消息

1 鼠标消息
1) 基本鼠标消息
    WM_LBUTTONDOWN   左键按下
    WM_LBUTTONUP     左键抬起
    WM_RBUTTONDOWN   右键按下
    WM_RBUTTONUP     右键抬起
    WM_MOUSEMOVE     鼠标移动
2) 双击消息
    WM_LBUTTONDBLCLK 左键双击 
    WM_RBUTTONDBLCLK 右键双击
3) 滚轮消息
    WM_MOUSEWHEEL    鼠标滚轮

2消息的参数

  WPARAM -当前键盘和鼠标按键状态,例如K_CONTROL/MK_SHIFT,MK_LBUTTON等。

LPARAM - 当前鼠标的坐标,坐标的原点是窗口客户区的左上角.

X坐标 - LOWORD(lParam),16

Y坐标 - HIWORD(lParam),16

参数具体内容和具体鼠标消息有稍微不同.

3 消息的使用  

3.1 基本鼠标消息,只需在窗口处理函数增加消息处理即可.当消息来临,获取鼠标和按键状态.

例如:

case WM_MOUSEMOVE:

int nX = LOWORD(lParam);

int nY = HIWORD(lParam);

  :坐标转换的函数 ClientToScreen可以将鼠标坐标转换为屏幕的坐标.

 

3.2 双击消息

 3.2.1 窗口注册要增加 CS_DBLCLKS类型wce.style = CS_DBLCLKS|...;

 3.2.2 在窗口处理函数中增加消息处理

 3.2.3 产生过程,例如:WM_LBUTTONDBLCLK

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

WM_LBUTTONUP

连续两次LBUTTONDOWN的时间间隔小于预定的双击时间间隔,就会产生LBUTTONDBLCLK消息.

双击时间间隔可以通过控制面板调整.

 

3.3 滚轮消息

 3.3.1 由于WM_MOUSEWHEEL需要Winnt4.0以上版本支持,所以需要包含在windows.h的头文件前,增加_WIN32_WINNT 宏定义,

      #define _WIN32_WINNT 0x0400

  3.3.2 在窗口处理函数中增加消息处理

  3.3.3 参数

 LPARAM 与其它鼠标消息类同

 WPARAM - LOWORD(WPARAM) 表示按键状态

  HIWORD(WPARAM) 滚轮滚动幅度,

120的倍数,可以为正负值.

正值: 滚轮向上滚动,一般窗口向上滚动

负值: 滚轮向下滚动,一般窗口向下滚动

// WinMouse.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "stdio.h"

HINSTANCE g_hInst   = NULL;
HANDLE    g_hStdOut = NULL;
int		  g_nXPos   = 0;
int       g_nYPos   = 0;
int		  g_nX1Rect = 0;
int		  g_nY1Rect = 0;
int       g_nX2Rect = 0;
int       g_nY2Rect = 0;

void PrintLog( LPSTR pszLog )

	WriteConsole( g_hStdOut,
		pszLog, strlen(pszLog), NULL, NULL );


void OnPaint( HWND hWnd, UINT nMsg,
 	 WPARAM wParam, LPARAM lParam )

	PAINTSTRUCT ps =  0 ;
	HDC hDC = BeginPaint( hWnd, &ps );

	CHAR szText[] = "Hello Mouse";
	TextOut( hDC, g_nXPos, g_nYPos, 
		szText, strlen(szText) );

	Rectangle( hDC, g_nX1Rect, g_nY1Rect,
		g_nX2Rect, g_nY2Rect );

	EndPaint( hWnd, &ps );


LRESULT CALLBACK WndProc( HWND hWnd,
						  UINT nMsg,
						  WPARAM wParam,
						  LPARAM lParam )

	switch( nMsg )
	
	case WM_PAINT:
		OnPaint( hWnd, nMsg, wParam, lParam );
		break;
	case WM_LBUTTONDOWN:
		
			PrintLog( "WM_LBUTTONDOWN\\n" );
			g_nX1Rect = LOWORD( lParam );
			g_nY1Rect = HIWORD( lParam );
		
		break;
	case WM_LBUTTONUP:
		
			PrintLog( "WM_LBUTTONUP\\n" );
			g_nX2Rect = LOWORD( lParam );
			g_nY2Rect = HIWORD( lParam );
			InvalidateRect( hWnd, NULL, TRUE );
		
		break;
	case WM_RBUTTONDOWN:
		PrintLog( "WM_RBUTTONDOWN\\n" );
		break;
	case WM_RBUTTONUP:
		PrintLog( "WM_RBUTTONUP\\n" );
		break;
	case WM_MOUSEMOVE:
		
			int nX = LOWORD(lParam);
			int nY = HIWORD(lParam);
			POINT ptScreen =  0 ;
			ptScreen.x = nX;
			ptScreen.y = nY;
			ClientToScreen( hWnd, &ptScreen );

			CHAR szText[260] =  0 ;
			sprintf( szText, 
				"WM_MOUSEMOVE: X=%d(%d),Y=%d(%d)\\n",
				nX, ptScreen.x, nY, ptScreen.y );
			PrintLog( szText );
			if( wParam & MK_CONTROL )
			
				PrintLog( "WM_MOUSEMOVE: MK_CONTROL\\n" );
			
			if( wParam & MK_LBUTTON )
			
				PrintLog( "WM_MOUSEMOVE: MK_LBUTTON\\n" );
			

			g_nXPos = LOWORD(lParam);
			g_nYPos = HIWORD(lParam);
			InvalidateRect( hWnd, NULL, TRUE );
		
		break;
	case WM_LBUTTONDBLCLK:
		PrintLog( "WM_LBUTTONDBLCLK\\n" );
		break;
	case WM_RBUTTONDBLCLK:
		PrintLog( "WM_RBUTTONDBLCLK\\n" );
		break;
	case WM_MOUSEWHEEL:
		
			short nDetla = HIWORD( wParam );
			int   nX     = LOWORD( lParam );
			int   nY     = HIWORD( lParam );
			CHAR szText[260] =  0 ;
			sprintf( szText, 
				"WM_MOUSEWHEEL: Detla=%d, X=%d,Y=%d\\n",
				nDetla, nX, nY );
			PrintLog( szText );
		
		break;
	case WM_DESTROY:
		PostQuitMessage( 0 );
		break;
	
	return DefWindowProc( hWnd, nMsg,
		wParam, lParam );


BOOL RegisterWnd( LPSTR pszClassName )

	WNDCLASSEX wce =  0 ;
	wce.cbSize		  = sizeof( wce );
	wce.cbClsExtra	  = 0;
	wce.cbWndExtra    = 0;
	wce.hbrBackground = HBRUSH(COLOR_WINDOW);
	wce.hCursor		  = NULL;
	wce.hIcon		  = NULL;
	wce.hIconSm       = NULL;
	wce.hInstance	  = g_hInst;
	wce.lpfnWndProc   = WndProc;
	wce.lpszClassName = pszClassName;
	wce.lpszMenuName  = NULL;
	wce.style         = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
	ATOM nAtom = RegisterClassEx( &wce );
	if( 0 == nAtom )
	
		return FALSE;
	
	return TRUE;


HWND CreateWnd( LPSTR pszClassName )

	HWND hWnd = CreateWindowEx( 0,
		pszClassName, "MyWnd", 
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, 
		CW_USEDEFAULT, NULL, NULL, 
		g_hInst, NULL );
	return hWnd;


void DisplayWnd( HWND hWnd )

	ShowWindow( hWnd, SW_SHOW );
	UpdateWindow( hWnd );


void Message( )

	MSG msg =  0 ;
	while( GetMessage( &msg, NULL, 0, 0 ) )
	
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	


void NewConsole( )

	AllocConsole( );
	g_hStdOut = 
		GetStdHandle( STD_OUTPUT_HANDLE );


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)

	NewConsole( );
 	g_hInst = hInstance;
	RegisterWnd( "MyWnd" );
	HWND hWnd = CreateWnd( "MyWnd" );
	DisplayWnd( hWnd );
	Message( );

	return 0;

定时器消息

1 定时器消息 WM_TIMER

  按照定时器设置时间段,自动向窗口发送一个定时器消息WM_TIMER.优先级比较低.

  定时器精度比较低,毫秒级别.消息产生时间也精度比较低.

2 消息和函数

  2.1 WM_TIMER  - 消息ID

wParam: 定时器的ID

lParam: 定时器的处理函数

 

  2.2 SetTimer  - 设置一个定时器

UINT SetTimer(

HWND hWnd, //窗口的句柄,可以为NULL

UINT nIDEvent,//定时器的ID,0为不预设ID

UINT uElapse,//定时器时间间隔,毫秒级别

TIMERPROC lpTimerFunc );//定时器的处理函数,可以为NULL

返回一个创建好的定时器ID

 

  2.3 KillTimer - 结束一个定时器

BOOL KillTimer(

  HWND hWnd,//窗口句柄

  UINT uIDEvent );//定时器ID

  2.4 TimerProc - 定时器处理函数

VOID CALLBACK TimerProc(

HWND hwnd, //窗口句柄

UINT uMsg, //WM_TIMER消息ID

UINT idEvent,//定时器ID

DWORD dwTime   );//当前系统时间

 

3 使用方式

3.1 创建定时器 SetTimer

3.1.1 指定窗口句柄HWND,那么TIMERPROC 参数可以为空,那么WM_TIMER消息将会发送给指定窗口.如果未指定, TIMERPROC不能空,必须指定定时器处理程序.

3.1.2 如果指定定时器ID,SetTimer会按照这个ID创建定时器,如果未指定,会返回一个创建定时器ID.

nTimerID = SetTimer( NULL, 0, 7 * 1000, TimerProc1 );

3.2 处理消息

  可以根据消息传入定时器ID,分别处理.

3.3 结束定时器

  在不使用时, KillTimer结束定时器.

  KillTimer( hWnd, 1000 );

// WinTimer.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "stdio.h"

HINSTANCE g_hInst   = NULL;
HANDLE    g_hStdOut = NULL;
UINT      g_nTimerID1 = 0;

void CALLBACK TimerProc1( HWND hWnd,
						  UINT nMsg,
						  UINT idEvent,
						  DWORD dwTime )

	CHAR szText[] = "TimerProc1: Hello Timer\\n";
	WriteConsole( g_hStdOut, szText,
		strlen(szText), NULL, NULL );


void OnCreate( HWND hWnd, UINT nMsg,
	WPARAM wParam, LPARAM lParam )

	//使用窗口处理函数,创建2个定时器
	SetTimer( hWnd, 1000, 3 * 1000, NULL );	
	SetTimer( hWnd, 1001, 5 * 1000, NULL );
	//使用窗口处理函数, 未指明定时器ID
	g_nTimerID1 = SetTimer( hWnd, 0, 
		1 * 1000, NULL );
	//使用TimerProc处理函数创建定时器
	SetTimer( hWnd, 1002, 7 * 1000, TimerProc1 );


void OnTimer( HWND hWnd, UINT nMsg,
	WPARAM wParam, LPARAM lParam )

	switch( wParam )
	
	case 1000:
		
			CHAR szText[] = "1000: Hello Timer\\n";
			WriteConsole( g_hStdOut, szText,
				strlen(szText), NULL, NULL );
		
		break;
	case 1001:
		
			CHAR szText[] = "1001: Hello Timer\\n";
			WriteConsole( g_hStdOut, szText,
				strlen(szText), NULL, NULL );
		
		break;
	default:
		
			CHAR szText[260] = 0;
			sprintf( szText, "%d: Hello Timer\\n",
				g_nTimerID1 );
			WriteConsole( g_hStdOut, szText,
				strlen(szText), NULL, NULL );
		
		break;
	


LRESULT CALLBACK WndProc( HWND hWnd,
						  UINT nMsg,
						  WPARAM wParam,
						  LPARAM lParam )

	switch( nMsg )
	
	case WM_CREATE:
		OnCreate( hWnd, nMsg, wParam, lParam );
		break;
	case WM_TIMER:
		OnTimer( hWnd, nMsg, wParam, lParam );
		break;
	case WM_DESTROY:
		KillTimer( hWnd, 1000 );
		PostQuitMessage( 0 );
		return 0;
	
	return DefWindowProc( hWnd, nMsg,
		wParam, lParam );


BOOL RegisterWnd( LPSTR pszClassName )

	WNDCLASSEX wce =  0 ;
	wce.cbSize        = sizeof( wce );
	wce.cbClsExtra    = 0;
	wce.cbWndExtra    = 0;
	wce.hbrBackground = HBRUSH(COLOR_WINDOW);
	wce.hCursor       = NULL;
	wce.hIcon	      = NULL;
	wce.hIconSm       = NULL;
	wce.hInstance     = g_hInst;
	wce.lpfnWndProc   = WndProc;
	wce.lpszClassName = pszClassName;
	wce.lpszMenuName  = NULL;
	wce.style         = CS_VREDRAW|CS_HREDRAW;

	ATOM nAtom = RegisterClassEx( &wce );
	if( 0 == nAtom )
	
		return FALSE;
	
	return TRUE;


HWND CreateWnd( LPSTR pszClassName )

	HWND hWnd = CreateWindowEx( 0,
		pszClassName, "MyWnd", 
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, NULL, NULL, 
		g_hInst, 0 );
	return hWnd;


void DisplayWnd( HWND hWnd )

	ShowWindow( hWnd, SW_SHOW );
	UpdateWindow( hWnd );


void Message( )

	MSG msg = 0;
	while ( GetMessage( &msg, NULL, 0, 0 ) )
	
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	


void NewConsole( )

	AllocConsole( );
	g_hStdOut = 
		GetStdHandle( STD_OUTPUT_HANDLE );


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)

	NewConsole( );
	g_hInst = hInstance;
	RegisterWnd( "MYWND" );
	HWND hWnd = CreateWnd( "MYWND" );
	DisplayWnd( hWnd );
	Message( );
	return 0;

菜单

  1 菜单基础
    菜单 - 每个菜单会有一个HMENU句柄
    菜单项 - 每个菜单项会有一个ID号,可以根据这个ID执行不同的操作
  2 菜单的使用
    2.1 菜单创建
      2.1.1 CreateMenu - MENU 菜单
      2.1.2 CreatePopupMenu - 
         POPUPMENU 弹出式菜单
      2.1.3 AppenedMenu - 增加菜单项
 BOOL AppendMenu(
 HMENU hMenu, //菜单句柄
 UINT uFlags, //菜单项标示
 UINT uIDNewItem, //菜单项的ID或者子菜单句柄
 LPCTSTR lpNewItem ); //菜单项的名称
uFlags: 
 MF_STRING - lpNewItem是一个字符串
 MF_POPUP  - uIDNewItem是一个子菜单句柄
 MF_SEPARATOR - 增加分隔项
 MF_CHECKED/MF_UNCHECKED -  设置和取消菜单项的对勾
 MF_DISABLED/MF_ENABLE - 菜单项禁止和允许状态
  2.2 菜单的命令响应
2.2.1 WM_COMMAND消息
 当用户点击菜单、按钮控件等时,系统会向窗口发送WM_COAMMD消息。
WPARAM:HIWORD - 通知消息标识
LOWORD - 菜单项的ID号
LPARAM:控件的句柄
2.2.2 命令处理
  根据菜单项的ID号作相应处理。
  
  2.3 菜单项的状态
2.3.1 WM_INITMENUPOPUP消息
  当用户点击菜单,显示弹出菜单之前,系统会向窗口发送WM_INITMENUPOPUP消息。
  WPARAM:是菜单句柄
  LPARAM:LOWORD - 菜单位置
  HIWORD - 是否是系统菜单
2.3.2 命令处理
  根据WPARAM的菜单句柄,使用MenuAPI函数,
  修改菜单状态。
CheckMenuItem - 选择  菜单项名称前面打上小对勾的效果。
EnableMenuItem - 允许和禁止
SetMenuItemInfo - 可以设置更多信息
// WinMenu.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "stdio.h"

HINSTANCE g_hInst   = NULL;
HANDLE    g_hStdOut = NULL;
BOOL      g_bCheckCut = FALSE;

void OnCreate( HWND hWnd, UINT nMsg,
	WPARAM wParam, LPARAM lParam )
	//创建主菜单
	HMENU hMainMenu = CreateMenu( );
	//创建子菜单
	HMENU hFileMenu = CreatePopupMenu( );
	//增加菜单项
	AppendMenu( hFileMenu, MF_STRING|MF_CHECKED, 1001, "新建(&N)");
	AppendMenu( hFileMenu, MF_SEPARATOR, 0, NULL );
	AppendMenu( hFileMenu, MF_STRING, 1002, "退出(&X)");
	AppendMenu( hMainMenu, MF_STRING|MF_POPUP, 
		(UINT)hFileMenu, "文件(&F)");

	HMENU hEditMenu = CreatePopupMenu( );
	AppendMenu( hEditMenu, MF_STRING, 1003, "剪切(&T)" );
	AppendMenu( hEditMenu, MF_STRING, 1004, "拷贝(&C)" );
	AppendMenu( hEditMenu, MF_STRING, 1005, "粘贴(&P)" );
	AppendMenu( hMainMenu, MF_STRING|MF_POPUP, 
		(UINT)hEditMenu, "编辑(&E)");

	HMENU hHelpMenu = CreatePopupMenu( );
	AppendMenu( hHelpMenu, MF_STRING, 1006, "帮助(&H)" );
	AppendMenu( hHelpMenu, MF_STRING, 1007, "关于(&A)" );
	AppendMenu( hMainMenu, MF_STRING|MF_POPUP, 
		(UINT)hHelpMenu, "帮助(&H)");
	//给窗口设置主菜单
	SetMenu( hWnd, hMainMenu );


void OnCommand( HWND hWnd, UINT nMsg, 
			   WPARAM wParam, LPARAM lParam )

	UINT nID = LOWORD( wParam );
	CHAR szText[260] = 0;
	sprintf( szText, "OnCommand: %d\\n",
		nID );
	WriteConsole( g_hStdOut, szText,
		strlen(szText), NULL, NULL );
	switch( nID )
	
	case 1002:
		PostQuitMessage( 0 );
		break;
	case 1003:
		g_bCheckCut = !g_bCheckCut;
		break;
	


void OnInitMenuPopup( HWND hWnd, UINT nMsg,
	WPARAM wParam, LPARAM lParam )

	CHAR szText[260] =  0 ;
	sprintf( szText, 
		"OnInitMenuPopup: WPARAM=%08X, LPARAM=%08X\\n", 
		wParam, lParam );
	WriteConsole( g_hStdOut, szText,
		strlen(szText), NULL, NULL );

	HMENU hMenu = (HMENU)wParam;
	if( TRUE == g_bCheckCut )
	
		CheckMenuItem( hMenu, 1003, 
			MF_CHECKED|MF_BYCOMMAND );
	
	else
	
		CheckMenuItem( hMenu, 1003, 
			MF_UNCHECKED|MF_BYCOMMAND );
	


LRESULT CALLBACK WndProc( HWND hWnd,
						  UINT nMsg,
						  WPARAM wParam,
						  LPARAM lParam )

	switch( nMsg )
	
	case WM_CREATE:
		OnCreate( hWnd, nMsg, wParam, lParam );
		break;
	case WM_COMMAND:
		OnCommand( hWnd, nMsg, wParam, lParam );
		break;
	case WM_INITMENUPOPUP:
		OnInitMenuPopup( hWnd, nMsg, wParam, lParam );
		break;
	case WM_DESTROY:
		PostQuitMessage( 0 );
		return 0;
	
	return DefWindowProc( hWnd, nMsg,
		wParam, lParam );


BOOL RegisterWnd( LPSTR pszClassName )

	WNDCLASSEX wce =  0 ;
	wce.cbSize        = sizeof( wce );
	wce.cbClsExtra    = 0;
	wce.cbWndExtra    = 0;
	wce.hbrBackground = HBRUSH(COLOR_WINDOW);
	wce.hCursor       = NULL;
	wce.hIcon	      = NULL;
	wce.hIconSm       = NULL;
	wce.hInstance     = g_hInst;
	wce.lpfnWndProc   = WndProc;
	wce.lpszClassName = pszClassName;
	wce.lpszMenuName  = NULL;
	wce.style         = CS_VREDRAW|CS_HREDRAW;

	ATOM nAtom = RegisterClassEx( &wce );
	if( 0 == nAtom )
	
		return FALSE;
	
	return TRUE;


HWND CreateWnd( LPSTR pszClassName )

	HWND hWnd = CreateWindowEx( 0,
		pszClassName, "MyWnd", 
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT,
		CW_USEDEFAULT, NULL, NULL, 
		g_hInst, 0 );
	return hWnd;


void DisplayWnd( HWND hWnd )

	ShowWindow( hWnd, SW_SHOW );
	UpdateWindow( hWnd );


void Message( )

	MSG msg = 0;
	while ( GetMessage( &msg, NULL, 0, 0 ) )
	
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	


void NewConsole( )

	AllocConsole( );
	g_hStdOut = 
		GetStdHandle( STD_OUTPUT_HANDLE );


int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)

	NewConsole( );
	g_hInst = hInstance;
	RegisterWnd( "MYWND" );
	HWND hWnd = CreateWnd( "MYWND" );
	DisplayWnd( hWnd );
	Message( );
	return 0;






以上是关于为啥鼠标左键按一下有时会变成双击了?的主要内容,如果未能解决你的问题,请参考以下文章

鼠标左键单击的脚本vbscript

OD怎样给鼠标左键单击下断点

我的鼠标左键按一下,就像是点击了两下,怎么搞的

鼠标单击变双击怎么解决

javascript如何屏蔽鼠标双击,或将双击变成单击

如何禁止鼠标的双击功能?