====== 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. [[http://termpaper.biz/buy-paper-online.php|buy admission essay]] ===== Projet ===== {{:data_grid:sdkdatagridview.zip|.zip Source de projet et Démo}} {{:data_grid:screenshotdemo.png|}} ===== 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é [[http://www.codeproject.com/KB/winsdk/msgcrackwizard.aspx|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 ===== 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 [[http://www.codeproject.com/KB/list/lvcustomdraw.aspx|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; }