Outils pour utilisateurs

Outils du site


fr:data_grid

Anatomie d'un contrôle personnalisé (grille de données simple)

David MacDermot 2008/09/17 19:55

Cet article décrit comment créer un contrôle personnalisé dans Pelles C. Le contrôle dans ce cas est une grille de données, utilisant comme composant un affichage de liste “listview” et une zone d'édition “editbox” avec un certain nombre de personnalisations pour améliorer l'apparence du contrôle. buy admission essay

Projet

Introduction

Le SDK de Windows fournit une sélection de contrôles d'interface utilisateur pour le développement d'applications qui couvre la plupart des bases lorsqu'il s'agit de conception d'interface utilisateur. Tôt ou tard,vous vous trouverez dans une situation ou vous souhaiteriez avoir certain éléments dans la boîte à outils qui n'y figure pas. Une recherche sur le web révèle un certain nombre de solutions intelligentes, mais la plupart d'entre eux utilisent des plates-formes MFC ou autre orientations d'objets modernes,qui se traduisent mal en Pelles C. Le plus souvent vous allez devoir créer votre propre contrôle à partir de zéro.La conception de votre propre contrôle ne doit pas être une affaire difficile

La classe des fenêtres

La classe décrit les propriétés communes des fenêtres de toutes les fenêtres qui seront créés à l'aide de cette classe. Nous allons utiliser la classe Windows pour encapsuler les méthodes associées à notre contrôle personnalisé et aussi pour fournir l'interface publique pour notre contrôle. L'interface publique pour notre contrôle sera le message de base de sorte qu'il n'existe qu'une seule méthode publique que nous créons dans le but d'enregistrer la classe des fenêtres. Cette méthode est appelée une seule fois pour enregistrer la classe. Les instances de contrôle sont créés en utilisant CreateWindow().

ATOM InitDataGridView(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;
    ATOM aReturn;
 
    wcex.cbSize          = sizeof(WNDCLASSEX);
    wcex.style           = CS_BYTEALIGNCLIENT;
    wcex.lpfnWndProc     = (WNDPROC)Grid_WndProc;
    wcex.cbClsExtra      = 0;
    wcex.cbWndExtra      = 0;
    wcex.hInstance       = hInstance;
    wcex.hCursor         = NULL;
    wcex.hbrBackground   = (HBRUSH) (GetStockObject(GRAY_BRUSH)); 
    wcex.lpszMenuName    = NULL;
    wcex.lpszClassName   = TEXT("DGridVwClass");
    wcex.hIcon           = NULL;
    wcex.hIconSm         = NULL;
 
    aReturn = RegisterClassEx(&wcex);
 
    return aReturn;
}

Cette méthode doit être appelée dans la procédure de l'application de point d'entrée, immédiatement après les appels vers l'initialisation des contrôles communs.

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
    INITCOMMONCONTROLSEX icc;
    WNDCLASSEX wcx;
 
    ghInstance = hInstance;
 
    /* Initialize common controls. Also needed for MANIFEST's */
 
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_WIN95_CLASSES /*|ICC_COOL_CLASSES|ICC_DATE_CLASSES|ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES*/;
    InitCommonControlsEx(&icc);
 
    /* Register our custom control*/
    InitDataGridView(hInstance);

La procédure de rappel

C'est là que nous allons fournir l'interface publique à notre contrôle, ainsi que gérer les messages standard de Windows que le système d'exploitation envoie vers la fenêtre de notre contrôle.

Les procédures de rappel peuvent devenir très longue et difficile à maintenir dans un contrôle complexe. Windows fournit une série de macros dans windowsx.h et comctrl.h afin d'améliorer grandement la lisibilité du code et d'aider à sortir le code en blocs d'événements gérables axés sur la méthode. Ces macros sont considérés comme des crackers de message et il existe des outils qui rendent leur mise en œuvre assez facile. J'ai utilisé Message Cracker Wizard pour générer les macros et les prototypes de méthode pour les messages standard de Windows, ce qui réduit considérablement la taille de la procédure de rappel.

La plupart des lignes de code dans cette section sont consacrés aux propres du contrôle des messages personnalisés et même imiter les acquéreurs et mutateurs de propriété communs à C++.

static LRESULT CALLBACK Grid_WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	// Pass along any list view specific messages transparently
	if(LVM_FIRST <= msg && msg <= LVM_FIRST + 181)
	{
		Grid_GetInstanceData(hwnd,&g_lpInst);
		return SNDMSG(g_lpInst->hwndList,msg,wParam,lParam);
	}
	switch(msg)
	{
		HANDLE_MSG (hwnd, WM_CTLCOLOREDIT, Grid_OnCtlColorEdit);
		HANDLE_MSG (hwnd, WM_CREATE, Grid_OnCreate);
		HANDLE_MSG (hwnd, WM_DESTROY, Grid_OnDestroy);
		HANDLE_MSG (hwnd, WM_GETDLGCODE, Grid_OnGetDlgCode);
		HANDLE_MSG (hwnd, WM_SIZE, Grid_OnSize);
		HANDLE_MSG (hwnd, WM_HSCROLL, Grid_OnHScroll);
		HANDLE_MSG (hwnd, WM_VSCROLL, Grid_OnVScroll);
		HANDLE_MSG (hwnd, WM_NOTIFY, Grid_OnNotify);
		HANDLE_MSG (hwnd, WM_SETCURSOR, Grid_OnSetCursor);
 
		case DGVM_GETLISTVIEWCONTROL:
			Grid_GetInstanceData(hwnd,&g_lpInst);
			return (LRESULT) g_lpInst->hwndList;
 
		case DGVM_GETEDITCONTROL:
			Grid_GetInstanceData(hwnd,&g_lpInst);
			return (LRESULT) g_lpInst->hwndEditor;
 
		case DGVM_GETCOLUMNCOUNT:
			Grid_GetInstanceData(hwnd,&g_lpInst);
			return (LRESULT) Grid_GetColumnCount(g_lpInst->hwndList);
                //
                // Skip a bunch of properties for the sake of brevity
                //
		case DGVM_SETROWHEIGHT:
			Grid_GetInstanceData(hwnd,&g_lpInst);
			Grid_SetRowHeight(g_lpInst->hwndList, (INT) wParam);
			return TRUE;
 
		default: return DefWindowProc (hwnd, msg, wParam, lParam);
	}
}

L'interface publique

Nous allons définir l'en-tête de l'interface publique dans le fichier du contrôle.

Windows réserve un bloc de messages pour les contrôles personnalisés en commençant par la définition WM_USER afin que nos messages soient WM_USER+, nous allons assigner. une certaine valeur. Les messages sont envoyés à un contrôle par l'intermédiaire SendMessage().ce qui nous donne deux paramètres que nous pourrons utiliser pour passer des valeurs à la procédure de rappel du contrôle. On peut aussi obtenir la valeur de retour de rappel à partir de SendMessage() . Je trouve qu'il est utile de définir des macros pour chaque message que de définir les messages eux-mêmes. Cela constitue un moyen de documenter chaque message dans le code du fichier d'en-tête au cours du développement et fournit une interface agréable syntaxiquement pour accéder aux propriétés du contrôle dans le code source.

En plus des macros de message j'ai également défini quelques macros d'assistance pour rendre plus facile l'ajout de colonnes et de lignes à notre contrôle. Notez que ces macros ne contiennent aucune références à notre messages personnalisés! Dans la procédure de rappel du contrôle toute les messages spécifiques listview sont transmis à la composante du contrôle de grille de listview.

/****************************************************************************/
// Messages
 
#define DGVM_GETLISTVIEWCONTROL WM_USER + 0x01
#define DGVM_GETEDITCONTROL WM_USER + 0x02
#define DGVM_GETCOLUMNCOUNT WM_USER + 0x03
#define DGVM_GETEDITORBACKCLR WM_USER + 0x04
#define DGVM_SETEDITORBACKCLR WM_USER + 0x05
#define DGVM_GETEDITORTEXTCLR WM_USER + 0x06
#define DGVM_SETEDITORTEXTCLR WM_USER + 0x07
#define DGVM_RESIZEABLEHEADER WM_USER + 0x08
#define DGVM_GETALTBACKCLR WM_USER + 0x09
#define DGVM_SETALTBACKCLR WM_USER + 0x0A
#define DGVM_GETALTTXTCLR WM_USER + 0x0B
#define DGVM_SETALTTXTCLR WM_USER + 0x0C
#define DGVM_SETDOUBLECLICKSELECT WM_USER + 0x0D
#define DGVM_FIRSTCOLUMNROWHEADERS WM_USER + 0x0E
#define DGVM_SETROWHEIGHT WM_USER + 0x0F
 
/****************************************************************************/
// Macroes
 
#define DataGridView_AddColumn(hGrid,nCol,iWidth,szColText){ \
	LVCOLUMN lv_c; lv_c.mask=LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; \
	lv_c.cx=iWidth; lv_c.pszText=(szColText); \
	ListView_InsertColumn(hGrid,nCol,&lv_c);}
 
#define DataGridView_AddColumns(hGrid,aryColTxt,iColCount) \
	for(int Col = 0; Col<iColCount;Col++) \
		DataGridView_AddColumn((hGrid),Col,strlen(aryColTxt[Col])*16,aryColTxt[Col]);
 
#define DataGridView_AddRow(hGrid,aryItemTxt,iColCount) \
	for(int Col = 0; Col<iColCount;){\
		LV_ITEM Lv_i; \
		Lv_i.mask=LVIF_TEXT; \
		if(0==Col) { \
			Lv_i.iItem=ListView_GetItemCount(hGrid); \
			Lv_i.iSubItem=Col; Lv_i.pszText=aryItemTxt[Col++]; \
	 		ListView_InsertItem(hGrid,&Lv_i);} \
		Lv_i.iSubItem=Col; \
		Lv_i.pszText=aryItemTxt[Col++]; \
		ListView_SetItem(hGrid,&Lv_i);}
 
#define DataGridView_GetEditorBkColor(hwnd)  (COLORREF)SNDMSG((hwnd),DGVM_GETEDITORBACKCLR,0,0L)
#define DataGridView_SetEditorBkColor(hwnd,clrBk)  (BOOL)SNDMSG((hwnd),DGVM_SETEDITORBACKCLR,0,(LPARAM)(COLORREF)(clrBk))
#define DataGridView_GetEditorTxtColor(hwnd)  (COLORREF)SNDMSG((hwnd),DGVM_GETEDITORTEXTCLR,0,0L)
#define DataGridView_SetEditorTxtColor(hwnd,clrBk)  (BOOL)SNDMSG((hwnd),DGVM_SETEDITORTEXTCLR,0,(LPARAM)(COLORREF)(clrBk))
#define DataGridView_GetListViewControl(hwnd)  (HWND)SNDMSG((hwnd),DGVM_GETLISTVIEWCONTROL,0,0L)
#define DataGridView_GetEditControl(hwnd)  (HWND)SNDMSG((hwnd),DGVM_GETEDITCONTROL,0,0L)
#define DataGridView_GetColumnCount(hwnd)  (INT)SNDMSG((hwnd),DGVM_GETCOLUMNCOUNT,0,0L)
#define DataGridView_GetRowCount(hwnd)  ListView_GetItemCount((hwnd))
#define DataGridView_SetResizableHeader(hwnd,fResizable)  (BOOL)SNDMSG((hwnd),DGVM_RESIZEABLEHEADER,(WPARAM)(fResizable),(LPARAM)0L)
#define DataGridView_GetAltBkColor(hwnd)  (COLORREF)SNDMSG((hwnd),DGVM_GETALTBACKCLR,0,0L)
#define DataGridView_SetAltBkColor(hwnd,clrBk,fPaintByRow)  (BOOL)SNDMSG((hwnd),DGVM_SETALTBACKCLR,(WPARAM)(fPaintByRow),(LPARAM)(COLORREF)(clrBk))
#define DataGridView_GetAltTextColor(hwnd)  (COLORREF)SNDMSG((hwnd),DGVM_GETALTTXTCLR,0,0L)
#define DataGridView_SetAltTextColor(hwnd,clrBk)  (BOOL)SNDMSG((hwnd),DGVM_SETALTTXTCLR,0,(LPARAM)(COLORREF)(clrBk))
#define DataGridView_SetDoubleClickSelect(hwnd,fDblClk)  (BOOL)SNDMSG((hwnd),DGVM_SETDOUBLECLICKSELECT,(WPARAM)(fDblClk),0L)
#define DataGridView_FirstColumnRowHeaders(hwnd,fDspRwHdr)  (BOOL)SNDMSG((hwnd),DGVM_FIRSTCOLUMNROWHEADERS,(WPARAM)(fDspRwHdr),0L)
#define DataGridView_SetRowHeight(hwnd,iHeight)  (BOOL)SNDMSG((hwnd),DGVM_SETROWHEIGHT,(WPARAM)(iHeight),0L)
 
/****************************************************************************/
// Prototypes de fonctions exportées
 
BOOL InitDataGridView(HINSTANCE hInstance);

Assurer le suivi de plusieurs instances

J'utilise une struct pour persister des propriétés de chaque instance du contrôle personnalisé.

typedef struct _tagINSTANCEDATA{
	HINSTANCE hInstance;
	HWND hwndList;
	HWND hwndEditor;
	LVHITTESTINFO hti;
	SCROLLINFO hsInfo;
	SCROLLINFO vsInfo;
	COLORREF Editor_TxtColr;
	COLORREF Editor_BkColr;
	COLORREF Alt_TxtColr;
	COLORREF Alt_BkColr;
	BOOL fPaintByRow;
	BOOL fRowHeaders;
	BOOL fResizableHeader;
	BOOL fDblClick;
}INSTANCEDATA, *LPINSTANCEDATA;

En plus de cela, il ya certaines méthodes standard que j'utilise pour gérer la création et l'attribution de .access to, et la destruction de cette structure.

static BOOL Control_GetInstanceData (HWND hControl, LPINSTANCEDATA *ppInstanceData)
{
	*ppInstanceData=(LPINSTANCEDATA)GetProp(hControl,(LPCSTR)"lpInsData");
	return NULL != *ppInstanceData;
}
 
static BOOL Control _CreateInstanceData (HWND hControl,  LPINSTANCEDATA pInstanceData)
{
	LPINSTANCEDATA pInst = (LPINSTANCEDATA)malloc(sizeof(INSTANCEDATA));
	memmove(pInst,pInstanceData,sizeof(INSTANCEDATA));
 
	return SetProp(hControl,"lpInsData",pInst);
}
 
static BOOL Control _FreeInstanceData (HWND hControl)
{
	LPINSTANCEDATA pInst;
	if(Control _GetInstanceData(hGrid,&pInst))
	{
		free ((LPINSTANCEDATA)pInst);
		RemoveProp(hControl,"lpInsData");
		return TRUE;
	}
	return FALSE;
}

Ici, je créer et j’attribue la structure dans le gestionnaire WM_CREATE du contrôle personnalisé

 static BOOL Grid_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
	INSTANCEDATA inst;
 
	// initialize hit test info and scroll info
	memset(&inst.hti, -1, sizeof(LVHITTESTINFO));
	memset(&inst.hsInfo, 0, sizeof(SCROLLINFO));
	memset(&inst.vsInfo, 0, sizeof(SCROLLINFO));
 
	// aller à hInstance
	inst.hInstance = lpCreateStruct->hInstance;
 
    // créer le contrôle ListView
    inst.hwndList = CreateListView(lpCreateStruct->hInstance, hwnd);
	if(NULL == inst.hwndList) return FALSE;
 
	// Couleurs par défaut de la  ListView
	inst.fPaintByRow = TRUE;
	inst.Alt_TxtColr = ListView_GetTextColor(inst.hwndList);
	inst.Alt_BkColr = ListView_GetBkColor(inst.hwndList);
 
	// ListView pseudoHeaders off par défaut
	inst.fRowHeaders = FALSE;
 
    inst.hwndEditor = CreateCellEditor(lpCreateStruct->hInstance, hwnd);
	if(NULL == inst.hwndEditor) return FALSE;
 
	// Couleurs par défaut de la cellule d'édition
	inst.Editor_BkColr = RGB(0,0,160);
	inst.Editor_TxtColr = RGB(255,255,255);
 
	// Comportement de la sélection par défaut de la cellule
	inst.fDblClick = FALSE;
 
	Grid_SetRowHeight(inst.hwndList, 20);
 
	// TODO: Create inializations for an array of cell edit controls
 
	return Grid_CreateInstanceData(hwnd,&inst);
}

Ici j'ai mis un pointeur vers la structure globale associée à cette instance du contrôle et alors libre de la structure quand il n'est plus nécessaire.

 static VOID Grid_OnDestroy(HWND hwnd)
{
	Grid_GetInstanceData(hwnd,&g_lpInst);
 
	DestroyWindow(g_lpInst->hwndList);
	DestroyWindow(g_lpInst->hwndEditor);
 
	Grid_FreeInstanceData(hwnd);
	PostQuitMessage(0);
}

Points d'intérêt

Dessin personnalisé d'une liste

Toutes les personnalisations et dessins des composants de la grille, sont gérées dans la notification NM_CUSTOMDRAW de la liste, ce qui inclut le dépouillement l'en-tête, quelque chose qui normalement ne peut pas ce faire, mais il est possible que parce que l'en-tête est sous-classée. Pour un tutoriel détaillé sur les personnalisations simples en utilisant cette méthode voir Trucs sympa à faire dans la liste des contrôles à l'aide dessin personnalisé.

LRESULT Grid_OnCustomDraw (HWND hwnd, LPNMLVCUSTOMDRAW lplvcd)
{
	Grid_GetInstanceData(hwnd,&g_lpInst);
 
    switch(lplvcd->nmcd.dwDrawStage) 
    {
		//Before the paint cycle begins
		//request notifications for individual listview items
        case CDDS_PREPAINT : return CDRF_NOTIFYITEMDRAW;
 
		//Before an item is drawn
 		//request notifications for individual listview subitems
        case CDDS_ITEMPREPAINT: return CDRF_NOTIFYSUBITEMDRAW;
 
        case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
        {
			// Color alternating Rows
			if(g_lpInst->fPaintByRow&&lplvcd->nmcd.dwItemSpec & 1)
			{
				lplvcd->clrText = g_lpInst->Alt_TxtColr;
				lplvcd->clrTextBk = g_lpInst->Alt_BkColr;
			}
			// Color alternating Cols
			else if(!g_lpInst->fPaintByRow&&lplvcd->iSubItem & 1)
			{
				lplvcd->clrText   = g_lpInst->Alt_TxtColr;
				lplvcd->clrTextBk = g_lpInst->Alt_BkColr;
			}
			else // Color default
			{
				lplvcd->clrText   = ListView_GetTextColor(g_lpInst->hwndList);
				lplvcd->clrTextBk = ListView_GetBkColor(g_lpInst->hwndList);
			}
			// Color active cell
			if( lplvcd->iSubItem == g_lpInst->hti.iSubItem &&
				lplvcd->nmcd.dwItemSpec == g_lpInst->hti.iItem)
			{
				lplvcd->clrText   = g_lpInst->Editor_TxtColr;
				lplvcd->clrTextBk = g_lpInst->Editor_BkColr;
			}
			// Skin Column headers
			if(g_lpInst->fRowHeaders)
			{
				HWND hHdr;
				HDITEM hdi;
				RECT rc;
				HDC hdc;
 
				hHdr = ListView_GetHeader(hwnd);
				Header_GetItem(hHdr,lplvcd->iSubItem, &hdi);
				Header_GetItemRect(hHdr,lplvcd->iSubItem, &rc);
				hdc=GetDC(hHdr);
 
				if(lplvcd->iSubItem == g_lpInst->hti.iSubItem)
					DrawEdge(hdc, &rc,EDGE_SUNKEN,BF_RECT|BF_SOFT|BF_MIDDLE);
				else
					DrawEdge(hdc, &rc,EDGE_RAISED,BF_RECT|BF_SOFT|BF_MIDDLE);
 
				rc.left+=6;
				rc.bottom-=2;
 
				SetBkMode(hdc,TRANSPARENT);
				SetTextColor(hdc,GetSysColor(COLOR_BTNTEXT));
				SelectObject(hdc,GetStockObject(DEFAULT_GUI_FONT));
				DrawText(hdc,hdi.pszText,lstrlen(hdi.pszText), &rc,DT_SINGLELINE|DT_BOTTOM|DT_LEFT);
				SetBkMode(hdc,OPAQUE);
 
				ReleaseDC(g_lpInst->hwndList,hdc);
			}
			// Draw Row headers
			if(lplvcd->iSubItem == 0&&g_lpInst->fRowHeaders)
			{
				static char buf [2048];
				RECT  rc;
				HDC  hdc;
 
				ListView_GetSubItemRect(g_lpInst->hwndList,
					lplvcd->nmcd.dwItemSpec,0,LVIR_LABEL,&rc);
 
				rc.left-=2;
				rc.top-=1;
				rc.bottom-=1;
 
				hdc=GetDC(g_lpInst->hwndList);
 
				if(lplvcd->nmcd.dwItemSpec==g_lpInst->hti.iItem)
					DrawEdge(hdc, &rc,EDGE_SUNKEN,BF_RECT|BF_SOFT|BF_MIDDLE);
				else
					DrawEdge(hdc, &rc,EDGE_RAISED,BF_RECT|BF_SOFT|BF_MIDDLE);
 
				ListView_GetItemText(g_lpInst->hwndList,lplvcd->nmcd.dwItemSpec,0,buf,sizeof buf);
 
				SetBkMode(hdc,TRANSPARENT);
				SetTextColor(hdc,GetSysColor(COLOR_BTNTEXT));
				SelectObject(hdc,GetStockObject(DEFAULT_GUI_FONT));
				DrawText(hdc,buf,lstrlen(buf), &rc,DT_SINGLELINE|DT_VCENTER|DT_CENTER);
				SetBkMode(hdc,OPAQUE);
 
				ReleaseDC(g_lpInst->hwndList,hdc);
 
				return CDRF_SKIPDEFAULT;
			}
			return CDRF_NEWFONT;
		}
    }
    return CDRF_DODEFAULT;
}

Obtenir le nombre de colonnes

La listview a une méthode pour obtenir le nombre d'éléments (nombre de lignes), mais ne fournit pas une méthode pour obtenir le nombre de colonnes. Il est donc nécessaire de prévoir un moyen indirect pour déterminer le nombre de colonnes. Une méthode serait d'obtenir un nombre d'éléments de tête de la listview, mais cela ne fonctionne pas si l'en-tête n'est pas encore visible. Ce qui suit est une méthode qui fonctionne en toutes circonstances.

static INT ListView_GetColumnCount(HWND hwnd)
{
	// Method: hittest low right corner of row
	BOOL fTempRow = FALSE;
	RECT rc;
	LVHITTESTINFO ht = {0};
 
	if(0 == ListView_GetItemCount(hwnd)) // No rows yet
	// Temporarily add a row in order to get the count
	{
		LV_ITEM Lv_i = {LVIF_TEXT,0,0,0,0,""};
		ListView_InsertItem(hwnd,&Lv_i);
		fTempRow = TRUE;
	}
	ListView_GetItemRect(hwnd,
		ListView_GetItemCount(hwnd)-1,
		&rc,LVIR_BOUNDS);
	ht.pt.x = rc.right-1;
	ht.pt.y = rc.bottom-1;
	ListView_SubItemHitTest(hwnd, &ht);
 
	if(fTempRow) ListView_DeleteItem(hwnd,0); //Delete temp row (if any)
 
	return ht.iSubItem;
}

Positionnement de l'éditeur sur redimensionnement

Le redimensionnement d'une colonne est assez simple. Mais ce qui se passe au contrôle d'édition que je suis le positionnement sur ​​la grille au cours d'une de redimensionnement? Il faut redimensionner et positionner en conséquence. C'est difficile à faire en temps réel mais avec un peu de fumée et de miroirs, j'ai pu lui donner l'air comme ça, c'est juste ce qui se passe.

Les messages d'en-tête de ListView une notification HDN_ITEMCHANGING qui signale un redimensionnement a commencé. Je cache le contrôle d'édition lorsque cela se produit, mais le sous-menu sous-jacente est peint de la même couleur que le contrôle d'édition de sorte qu'il ressemble le contrôle d'édition est l'étirement en temps réel. Tout ce que je besoin de faire lorsque le redimensionnement est terminée sont de repositionner le contrôle d'édition et de le montrer encore. Malheureusement, il n'existe aucun moyen direct pour déterminer si un redimensionnement est terminée

Je dois avouer que celui-ci m'a laissé bouche bée pendant un certain temps. J'ai joué avec l'affichage de la liste et observer de près ce qui se passait au cours d'une de redimensionnement.

  • Mouse down on header
  • Mouse move
  • Mouse up

After attempting a mouse based approach with limited success I noticed something else: the cursor changes when resizing and after a resize is completed. I now had an easy way to determine the completion of the resize.

static BOOL  Grid_OnNotify(HWND hwnd, INT id, LPNMHDR pnm)
{
	//
	// If this is a resizable grid, initiate column resizing
	//  resizing is concluded in the Grid_OnSetCursor event handler
	//
	if(HDN_ITEMCHANGING==pnm->code)
		return Grid_OnColumnResize(hwnd, pnm);
BOOL Grid_OnColumnResize(HWND hwnd, LPNMHDR pnm)
{
	Grid_GetInstanceData(hwnd,&g_lpInst);
 
	if(!g_lpInst->fResizableHeader) return TRUE;
 
	ShowWindow(g_lpInst->hwndEditor,FALSE);
	g_fsizeCol = TRUE;
 
	return FALSE;
}
BOOL Grid_OnSetCursor(HWND hwnd, HWND hwndCursor, UINT codeHitTest, UINT msg)
{
	//
	// In Grid_OnColumnResize() we set g_fsizeCol when we clicked on the listview header;
	//  now we have a means of knowing that the uncaptured cursor has
	//  just slipped off the header. 
	//
	if(g_fsizeCol)
	{
		RECT rc;
		Grid_GetInstanceData(hwnd,&g_lpInst);
 
		//RePosition the editor
		ListView_GetSubItemRect(g_lpInst->hwndList,g_lpInst->hti.iItem,g_lpInst->hti.iSubItem,LVIR_LABEL,&rc);
		MapWindowPoints(g_lpInst->hwndList, hwnd, (LPPOINT)&rc.left,2);
		MoveWindow(g_lpInst->hwndEditor,
			rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,TRUE);
 
		//Show the editor
		ShowWindow(g_lpInst->hwndEditor,TRUE);
 
		g_fsizeCol = FALSE;
	}
	return FALSE; //Since this is not the standard use of this event don't handle it.
}

Défilement

Obtenir le fonctionnement du contrôle du défilement en plein écrand fut un défi. Je voudrais redimensionner les colonnes de la grille de sorte que la barre de défilement horizontale apparaît, faires défiler l'écran, puis redimensionner les colonnes pour retourner à la taille initiale. Cela a provoqué la disparition de la barre de défilement en laissant le point de vue décalé et une partie du contrôle n'est plus visible. La solution est de conserver l'information verticale et horizontale de défilement dans le contrôle de sorte qu'il n'a pas été perdu lorsque les barres disparu. Dans le cas de défilement horizontal, le contrôle casseroles à l'origine, si la taille de la liste réduit pour tenir dans la fenêtre apparentés. Unités de défilement verticales reflètent la hauteur des lignes et des unités horizontales qui sont fixées par la largeur de la première colonne visible. Je trouve que cela donne le contrôle du comportement plus doux de défilement tout en évitant les lignes veuves ou orphelins. Surtout lorsque vous naviguez dans le contrôle à partir du clavier.

Le défilement horizontale:

void Grid_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
	Grid_GetInstanceData(hwnd,&g_lpInst);
	RECT rc;
	GetClientRect(hwnd,&rc);
	int line = ListView_GetFirstVisibleColWidth(g_lpInst->hwndList, &rc);
	int page = line * ((int)(rc.right-rc.left) / line);
 
	int oldPos = g_lpInst->hsInfo.nPos;
 
	switch (code)
	{
		case SB_BOTTOM:			//Scrolls to the lower right.
			g_lpInst->hsInfo.nPos = g_lpInst->hsInfo.nMax;
			break;
 
		case SB_TOP:			//Scrolls to the lower left.
			g_lpInst->hsInfo.nPos = g_lpInst->hsInfo.nMin;
			break;
 
		case SB_ENDSCROLL:		//Ends scroll.
			break;
 
		case SB_LINELEFT:		//Scrolls left by one unit.
			g_lpInst->hsInfo.nPos -= line;
			break;
 
		case SB_LINERIGHT:		//Scrolls right by one unit.
			g_lpInst->hsInfo.nPos += line;
			break;
 
		case SB_PAGELEFT:		//Scrolls left by the width of the window.
			g_lpInst->hsInfo.nPos -= page;
			break;
 
		case SB_PAGERIGHT:		//Scrolls right by the width of the window.
			g_lpInst->hsInfo.nPos += page;
			break;
 
		case SB_THUMBPOSITION:	//Scrolls to the absolute position. The current position is specified by the Pos parameter.
			g_lpInst->hsInfo.nPos = pos;
			break;
 
		case SB_THUMBTRACK:		//Drags scroll box to the specified position. The current position is specified by the Pos parameter.
			g_lpInst->hsInfo.nPos = pos;	
			break;				
	}
	// Ensure that we do not scroll beyond visible boundaries
	g_lpInst->hsInfo.nPos = g_lpInst->hsInfo.nPos < g_lpInst->hsInfo.nMin ||
		(code == SB_LINELEFT && g_lpInst->hsInfo.nPos < line) ?
			 g_lpInst->hsInfo.nMin : g_lpInst->hsInfo.nPos;
	g_lpInst->hsInfo.nPos = g_lpInst->hsInfo.nPos > g_lpInst->hsInfo.nMax - g_lpInst->hsInfo.nPage ?
		 g_lpInst->hsInfo.nMax - g_lpInst->hsInfo.nPage : g_lpInst->hsInfo.nPos;
 
	g_lpInst->hsInfo.fMask = SIF_POS;
	SetScrollInfo(hwnd,SB_HORZ,&g_lpInst->hsInfo,TRUE);
	Scroll(hwnd,-(g_lpInst->hsInfo.nPos-oldPos),0);
}

Le défilement vertical:

static VOID Grid_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, INT pos)
{
	Grid_GetInstanceData(hwnd,&g_lpInst);
	RECT rc;
	GetClientRect(hwnd,&rc);
	int line = ListView_GetRowHeight(g_lpInst->hwndList, &rc);
	int page = line * ((int)(rc.bottom-rc.top) / line);
 
	pos = line * (pos / line);//ScrollPos in increments of line
 
	int oldPos = g_lpInst->vsInfo.nPos;
 
	switch (code)
	{
		case SB_BOTTOM:			//Scrolls to the lower right.
			g_lpInst->vsInfo.nPos = g_lpInst->vsInfo.nMax;
			break;
 
		case SB_TOP:			//Scrolls to the upper left.
			g_lpInst->vsInfo.nPos = g_lpInst->vsInfo.nMin;
			break;
 
		case SB_ENDSCROLL:		//Ends scroll.
			break;
 
		case SB_LINEDOWN:		//Scrolls one line down.
			g_lpInst->vsInfo.nPos += line;
			break;
 
		case SB_LINEUP:			//Scrolls one line up.
			g_lpInst->vsInfo.nPos -= line;
			break;
 
		case SB_PAGEDOWN:		//Scrolls one page down.
			g_lpInst->vsInfo.nPos += page;
			break;
 
		case SB_PAGEUP:			//Scrolls one page up.
			g_lpInst->vsInfo.nPos -= page;
			break;
 
		case SB_THUMBPOSITION:			//Scrolls to the absolute position. The current position is specified by the nPos parameter.
			g_lpInst->vsInfo.nPos = pos;
			break;
 
		case SB_THUMBTRACK:			//Drags scroll box to the specified position. The current position is specified by the nPos parameter.
			g_lpInst->vsInfo.nPos = pos;
			break;							
	}
	g_lpInst->vsInfo.nPos = g_lpInst->vsInfo.nPos < g_lpInst->vsInfo.nMin ? g_lpInst->vsInfo.nMin : g_lpInst->vsInfo.nPos;
	g_lpInst->vsInfo.nPos = g_lpInst->vsInfo.nPos > g_lpInst->vsInfo.nMax - g_lpInst->vsInfo.nPage ? g_lpInst->vsInfo.nMax - g_lpInst->vsInfo.nPage : g_lpInst->vsInfo.nPos;
 
	// In order to reduce flicker in the header we position the header
	// before or after the scroll depending on the direction of scroll
 
	if(oldPos < pos) PinHeaderToTop(ListView_GetHeader(g_lpInst->hwndList)); 
 
	g_lpInst->vsInfo.fMask = SIF_POS;
	SetScrollInfo(hwnd,SB_VERT,&g_lpInst->vsInfo,TRUE);
	Scroll(hwnd,0,-(g_lpInst->vsInfo.nPos-oldPos));
 
	if(oldPos > pos)PinHeaderToTop(ListView_GetHeader(g_lpInst->hwndList));
}

Appel des événements de défilement de la ListView_KeyProc de sorte que la zone d'édition reste en vue

static LRESULT CALLBACK ListView_KeyProc (HWND hList, UINT msg, WPARAM wParam, LPARAM lParam)
{
	// Remarque: Les données d'instance sont attachées à une ListView aparentée
	static RECT rc, rcParent;
	static char buf[2048];
 
	if(WM_DESTROY==msg) // UnSubclass the ListView Control
	{
		SetWindowLong(hList,GWL_WNDPROC,(DWORD)GetProp(hList,"Wprc"));
		RemoveProp(hList,"Wprc");
		return 0;
	}
	else if(WM_KEYDOWN==msg)
	{
		HWND hParent = GetParent(hList);
 
		//
		//  Snip
		//
 
		//Adjust Scrolls
		switch(wParam)
		{
			case VK_HOME:
					SNDMSG(hParent,WM_HSCROLL,SB_TOP,0);
				break;
			case VK_END:
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.right)))
					SNDMSG(hParent,WM_HSCROLL,SB_BOTTOM,0);
				break;
			case VK_PRIOR:
					SNDMSG(hParent,WM_VSCROLL,SB_TOP,0);
				break;
			case VK_NEXT:
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.right)))
					SNDMSG(hParent,WM_VSCROLL,SB_BOTTOM,0);
				break;
			case VK_UP:
			{
				RECT rcHeader;
				GetClientRect(ListView_GetHeader(hList),&rcHeader);
 
				//Keep the edit box from overlaying the header bar
				rcParent.top += (rcHeader.bottom-rcHeader.top);
 
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.left)))
					SNDMSG(hParent,WM_VSCROLL,SB_LINEUP,0);
			}
				break;
			case VK_DOWN:
				rcParent.bottom -= ListView_GetRowHeight(hList, &rcParent);
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.left)))
					SNDMSG(hParent,WM_VSCROLL,SB_LINEDOWN,0);
				break;
			case VK_LEFT:
				rcParent.left += ListView_GetFirstVisibleColWidth(hList, &rcParent);
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.right)))
					SNDMSG(hParent,WM_HSCROLL,SB_LINELEFT,0);
				break;
			case VK_RIGHT:
				rcParent.right -= ListView_GetLastVisibleColWidth(hList, &rcParent);
				if(!PtInRect(&rcParent,*((LPPOINT)&rc.left)))
					SNDMSG(hParent,WM_HSCROLL,SB_LINERIGHT,0);
				break;
		}
		return 0;
	}
fr/data_grid.txt · Dernière modification: 2012/10/09 09:20 par darcybarron