void CVideoMarkup::EmptyTrash() { // delete all the samples in the "trash" group int iItem = ListView_GetNextItem(m_sampleListView, -1, LVNI_ALL); while (iItem != -1) { LVITEM lvi; lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_GROUPID; lvi.state = 0; lvi.stateMask = 0; lvi.iItem = iItem; lvi.iSubItem = 0; ListView_GetItem(m_sampleListView, &lvi); int iNextItem = ListView_GetNextItem(m_sampleListView, iItem, LVNI_ALL); if (lvi.iGroupId == GROUPID_TRASH) { // remove this sample from the listview UINT sampleIdToDelete = ListView_MapIndexToID(m_sampleListView, iItem); ListView_DeleteItem(m_sampleListView, iItem); // update sample in training set too sampleSet.RemoveSample(sampleIdToDelete); // indices have changed so we need to start at the beginning of the list again iNextItem = ListView_GetNextItem(m_sampleListView, -1, LVNI_ALL); } iItem = iNextItem; } }
// Constructor for motion training sample (includes motion history image) TrainingSample::TrainingSample(IplImage* srcImage, IplImage* motionHist, HWND lc, HIMAGELIST il, Rect bounds, int groupId) { // this constructor should only be called for motion sample type assert(groupId == GROUPID_MOTIONSAMPLES); hwndListControl = lc; hImageList = il; iGroupId = groupId; iOrigId = groupId; selectBounds = bounds; motionTrack.clear(); fullImageCopy = cvCreateImage(cvSize(bounds.Width,bounds.Height),IPL_DEPTH_8U, 3); motionHistory = cvCreateImage(cvSize(motionHist->width,motionHist->height),IPL_DEPTH_32F, 1); resizedImage = cvCreateImage(cvSize(LISTVIEW_SAMPLE_X,LISTVIEW_SAMPLE_Y),IPL_DEPTH_8U, 3); bmpImage = new Bitmap(LISTVIEW_SAMPLE_X, LISTVIEW_SAMPLE_Y, PixelFormat24bppRGB); cvSetImageROI(srcImage, cvRect( bounds.X, bounds.Y, bounds.Width, bounds.Height)); cvCopyImage(srcImage, fullImageCopy); if (srcImage->width >= LISTVIEW_SAMPLE_X && srcImage->height >= LISTVIEW_SAMPLE_Y) { cvResize(srcImage, resizedImage, CV_INTER_AREA); } else { cvResize(srcImage, resizedImage, CV_INTER_LINEAR); } cvResetImageROI(srcImage); // copy entire frame motion history image (motion history analysis doesn't work as well on partial frame) cvCopyImage(motionHist, motionHistory); IplToBitmap(resizedImage, bmpImage); bmpImage->GetHBITMAP(NULL, &hbmImage); // Add image to imagelist int imgIndex = ImageList_Add(hImageList, hbmImage, NULL); // Add item to list view lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_GROUPID; lvi.state = 0; lvi.stateMask = 0; lvi.iGroupId = groupId; lvi.iItem = imgIndex; lvi.iImage = imgIndex; lvi.iSubItem = 0; int newListItemPos = ListView_InsertItem(hwndListControl, &lvi); id = ListView_MapIndexToID(hwndListControl, newListItemPos); }
// Constructor for loading sample from image file TrainingSample::TrainingSample(char *filename, HWND lc, HIMAGELIST il, int groupId) { // this constructor should only be called for positive and negative sample types assert((groupId == GROUPID_POSSAMPLES) || (groupId == GROUPID_NEGSAMPLES)); hwndListControl = lc; hImageList = il; iGroupId = groupId; iOrigId = groupId; motionTrack.clear(); motionHistory = NULL; fullImageCopy = cvLoadImage(filename, 1); if (fullImageCopy == NULL) { fullImageCopy = cvCreateImage(cvSize(LISTVIEW_SAMPLE_X,LISTVIEW_SAMPLE_Y),IPL_DEPTH_8U, 3); cvZero(fullImageCopy); } resizedImage = cvCreateImage(cvSize(LISTVIEW_SAMPLE_X,LISTVIEW_SAMPLE_Y),IPL_DEPTH_8U, 3); bmpImage = new Bitmap(LISTVIEW_SAMPLE_X, LISTVIEW_SAMPLE_Y, PixelFormat24bppRGB); if (fullImageCopy->width >= LISTVIEW_SAMPLE_X && fullImageCopy->height >= LISTVIEW_SAMPLE_Y) { cvResize(fullImageCopy, resizedImage, CV_INTER_AREA); } else { cvResize(fullImageCopy, resizedImage, CV_INTER_LINEAR); } IplToBitmap(resizedImage, bmpImage); bmpImage->GetHBITMAP(NULL, &hbmImage); // Add image to imagelist int imgIndex = ImageList_Add(hImageList, hbmImage, NULL); // Add item to list view lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_GROUPID; lvi.state = 0; lvi.stateMask = 0; lvi.iGroupId = groupId; lvi.iItem = imgIndex; lvi.iImage = imgIndex; lvi.iSubItem = 0; int newListItemPos = ListView_InsertItem(hwndListControl, &lvi); id = ListView_MapIndexToID(hwndListControl, newListItemPos); }
// Constructor for range sample (includes motion track) TrainingSample::TrainingSample(IplImage *frame, MotionTrack mt, HWND lc, HIMAGELIST il, int groupId) { // this constructor should only be called for range sample type assert(groupId == GROUPID_RANGESAMPLES); hwndListControl = lc; hImageList = il; iGroupId = groupId; iOrigId = groupId; motionTrack = mt; motionHistory = NULL; fullImageCopy = cvCreateImage(cvSize(frame->width,frame->height),IPL_DEPTH_8U, 3); cvZero(fullImageCopy); cvAddWeighted(frame, 0.5, fullImageCopy, 0.5, 0.0, fullImageCopy); // draw the trajectory in the sample image Template t("", mt); DrawTrack(fullImageCopy, t.m_points, CV_RGB(100,255,100), 3, GESTURE_SQUARE_SIZE); resizedImage = cvCreateImage(cvSize(LISTVIEW_SAMPLE_X,LISTVIEW_SAMPLE_Y),IPL_DEPTH_8U, 3); bmpImage = new Bitmap(LISTVIEW_SAMPLE_X, LISTVIEW_SAMPLE_Y, PixelFormat24bppRGB); cvResize(fullImageCopy, resizedImage, CV_INTER_AREA); IplToBitmap(resizedImage, bmpImage); bmpImage->GetHBITMAP(NULL, &hbmImage); // Add image to imagelist int imgIndex = ImageList_Add(hImageList, hbmImage, NULL); // Add item to list view lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_GROUPID; lvi.state = 0; lvi.stateMask = 0; lvi.iGroupId = groupId; lvi.iItem = imgIndex; lvi.iImage = imgIndex; lvi.iSubItem = 0; int newListItemPos = ListView_InsertItem(hwndListControl, &lvi); id = ListView_MapIndexToID(hwndListControl, newListItemPos); }
LRESULT CVideoMarkup::OnButtonUp( UINT, WPARAM, LPARAM lParam, BOOL&) { POINT p; p.x = LOWORD(lParam); p.y = HIWORD(lParam); if (draggingIcon) { // we just completed an icon drag // End the drag-and-drop process draggingIcon = FALSE; ImageList_DragLeave(m_sampleListView); ImageList_EndDrag(); ImageList_Destroy(hDragImageList); SetCursor(LoadCursor(NULL, IDC_ARROW)); ReleaseCapture(); // Determine the position of the drop point LVHITTESTINFO lvhti; lvhti.pt = p; ClientToScreen(&lvhti.pt); ::ScreenToClient(m_sampleListView, &lvhti.pt); ListView_HitTestEx(m_sampleListView, &lvhti); CRect posRect, negRect, motionRect, rangeRect; ListView_GetGroupRect(m_sampleListView, GROUPID_POSSAMPLES, LVGGR_GROUP, &posRect); ListView_GetGroupRect(m_sampleListView, GROUPID_NEGSAMPLES, LVGGR_GROUP, &negRect); ListView_GetGroupRect(m_sampleListView, GROUPID_MOTIONSAMPLES, LVGGR_GROUP, &motionRect); ListView_GetGroupRect(m_sampleListView, GROUPID_RANGESAMPLES, LVGGR_GROUP, &rangeRect); int newGroupId; if (posRect.PtInRect(lvhti.pt)) newGroupId = GROUPID_POSSAMPLES; else if (negRect.PtInRect(lvhti.pt)) newGroupId = GROUPID_NEGSAMPLES; else if (motionRect.PtInRect(lvhti.pt)) newGroupId = GROUPID_MOTIONSAMPLES; else if (rangeRect.PtInRect(lvhti.pt)) newGroupId = GROUPID_RANGESAMPLES; else newGroupId = GROUPID_TRASH; // update group membership of selected items based on drop location int numSelected = ListView_GetSelectedCount(m_sampleListView); int iSelection = -1; for (int iIndex=0; iIndex<numSelected; iIndex++) { // retrieve the selected item LVITEM lvi; iSelection = ListView_GetNextItem(m_sampleListView, iSelection, LVNI_SELECTED); lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_GROUPID; lvi.state = 0; lvi.stateMask = 0; lvi.iItem = iSelection; lvi.iSubItem = 0; ListView_GetItem(m_sampleListView, &lvi); // Get the ID of this selected item UINT sampleId = ListView_MapIndexToID(m_sampleListView, iSelection); // test if this is an allowable group membership change int origGroupId = sampleSet.GetOriginalSampleGroup(sampleId); if (!GroupTransitionIsAllowed(origGroupId, newGroupId)) { // this is not a valid change so we'll move to the next item continue; } // update sample group in training set sampleSet.SetSampleGroup(sampleId, newGroupId); // Update item in list view with new group id lvi.iGroupId = newGroupId; ListView_SetItem(m_sampleListView, &lvi); } m_sampleListView.Invalidate(FALSE); } else if (m_videoLoader.videoLoaded && selectingRegion) { // we just finished drawing a selection ClipCursor(NULL); // restore full cursor movement if (!m_videoRect.PtInRect(p)) { InvalidateRect(&m_videoRect,FALSE); return 0; } selectingRegion = false; Rect selectRect; selectRect.X = (INT) min(selectStart.X, selectCurrent.X); selectRect.Y = (INT) min(selectStart.Y, selectCurrent.Y); selectRect.Width = (INT) abs(selectStart.X - selectCurrent.X); selectRect.Height = (INT) abs(selectStart.Y - selectCurrent.Y); Rect drawBounds(0,0,VIDEO_X,VIDEO_Y); selectRect.Intersect(drawBounds); double scaleX = ((double)m_videoLoader.videoX) / ((double)VIDEO_X); double scaleY = ((double)m_videoLoader.videoY) / ((double)VIDEO_Y); selectRect.X = (INT) (scaleX * selectRect.X); selectRect.Y = (INT) (scaleY * selectRect.Y); selectRect.Width = (INT) (scaleX * selectRect.Width); selectRect.Height = (INT) (scaleY * selectRect.Height); // discard tiny samples since they won't help if ((selectRect.Width > 10) && (selectRect.Height > 10)) { TrainingSample *sample; // if we're in motion mode, the behavior is a little special if (recognizerMode == MOTION_FILTER) { sample = new TrainingSample(m_videoLoader.copyFrame, m_videoLoader.GetMotionHistory(), m_sampleListView, m_hImageList, selectRect, GROUPID_MOTIONSAMPLES); } else { sample = new TrainingSample(m_videoLoader.copyFrame, m_sampleListView, m_hImageList, selectRect, currentGroupId); } sampleSet.AddSample(sample); } InvalidateRect(&m_videoRect, FALSE); } return 0; }
LRESULT CALLBACK PhpExtendedListViewWndProc( __in HWND hwnd, __in UINT uMsg, __in WPARAM wParam, __in LPARAM lParam ) { PPH_EXTLV_CONTEXT context; WNDPROC oldWndProc; context = (PPH_EXTLV_CONTEXT)GetProp(hwnd, PhpMakeExtLvContextAtom()); oldWndProc = context->OldWndProc; switch (uMsg) { case WM_DESTROY: { SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)oldWndProc); if (context->TickHashtable) PhDereferenceObject(context->TickHashtable); PhFree(context); RemoveProp(hwnd, PhpMakeExtLvContextAtom()); } break; case WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; switch (header->code) { case HDN_ITEMCLICK: { HWND headerHandle; headerHandle = (HWND)CallWindowProc(context->OldWndProc, hwnd, LVM_GETHEADER, 0, 0); if (header->hwndFrom == headerHandle) { LPNMHEADER header2 = (LPNMHEADER)header; if (header2->iItem == context->SortColumn) { if (context->TriState) { if (context->SortOrder == AscendingSortOrder) context->SortOrder = DescendingSortOrder; else if (context->SortOrder == DescendingSortOrder) context->SortOrder = NoSortOrder; else context->SortOrder = AscendingSortOrder; } else { if (context->SortOrder == AscendingSortOrder) context->SortOrder = DescendingSortOrder; else context->SortOrder = AscendingSortOrder; } } else { context->SortColumn = header2->iItem; context->SortOrder = AscendingSortOrder; } PhSetHeaderSortIcon(headerHandle, context->SortColumn, context->SortOrder); ExtendedListView_SortItems(hwnd); } } break; } } break; case WM_REFLECT + WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; switch (header->code) { case NM_CUSTOMDRAW: { if (header->hwndFrom == hwnd) { LPNMLVCUSTOMDRAW customDraw = (LPNMLVCUSTOMDRAW)header; switch (customDraw->nmcd.dwDrawStage) { case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW; case CDDS_ITEMPREPAINT: { LVITEM item; PH_ITEM_STATE itemState; BOOLEAN colorChanged = FALSE; HFONT newFont = NULL; item.mask = LVIF_STATE; item.iItem = (INT)customDraw->nmcd.dwItemSpec; item.iSubItem = 0; item.stateMask = LVIS_STATEIMAGEMASK; CallWindowProc(oldWndProc, hwnd, LVM_GETITEM, 0, (LPARAM)&item); itemState = PH_GET_ITEM_STATE(item.state); if (!context->EnableState || itemState == NormalItemState) { if (context->ItemColorFunction) { customDraw->clrTextBk = context->ItemColorFunction( (INT)customDraw->nmcd.dwItemSpec, (PVOID)customDraw->nmcd.lItemlParam, context->Context ); colorChanged = TRUE; } if (context->ItemFontFunction) { newFont = context->ItemFontFunction( (INT)customDraw->nmcd.dwItemSpec, (PVOID)customDraw->nmcd.lItemlParam, context->Context ); } if (newFont) SelectObject(customDraw->nmcd.hdc, newFont); } else if (itemState == NewItemState) { customDraw->clrTextBk = context->NewColor; colorChanged = TRUE; } else if (itemState == RemovingItemState) { customDraw->clrTextBk = context->RemovingColor; colorChanged = TRUE; } if (colorChanged) { if (PhGetColorBrightness(customDraw->clrTextBk) > 100) // slightly less than half customDraw->clrText = RGB(0x00, 0x00, 0x00); else customDraw->clrText = RGB(0xff, 0xff, 0xff); } if (!newFont) return CDRF_DODEFAULT; else return CDRF_NEWFONT; } break; } } } break; } } break; case WM_SETCURSOR: { if (context->Cursor) { SetCursor(context->Cursor); return TRUE; } } break; case WM_UPDATEUISTATE: { // Disable focus rectangles by setting or masking out the flag where appropriate. switch (LOWORD(wParam)) { case UIS_SET: wParam |= UISF_HIDEFOCUS << 16; break; case UIS_CLEAR: case UIS_INITIALIZE: wParam &= ~(UISF_HIDEFOCUS << 16); break; } } break; case LVM_INSERTITEM: { LPLVITEM item = (LPLVITEM)lParam; INT index; if (!context->EnableState) break; // pass through if (!(item->mask & LVIF_STATE)) { item->mask |= LVIF_STATE; item->stateMask = LVIS_STATEIMAGEMASK; item->state = 0; } else { item->stateMask |= LVIS_STATEIMAGEMASK; item->state &= ~LVIS_STATEIMAGEMASK; } if (context->EnableStateHighlighting > 0) { item->state |= INDEXTOSTATEIMAGEMASK(NewItemState); } else { item->state = NormalItemState; } if ((index = (INT)CallWindowProc(oldWndProc, hwnd, LVM_INSERTITEM, 0, (LPARAM)item)) == -1) return -1; if (context->EnableStateHighlighting > 0) { PH_TICK_ENTRY entry; PhpEnsureTickHashtableCreated(context); entry.Id = ListView_MapIndexToID(hwnd, index); entry.TickCount = GetTickCount(); PhAddEntryHashtable(context->TickHashtable, &entry); } return index; } case LVM_DELETEITEM: { if (!context->EnableState) break; // pass through if (context->EnableStateHighlighting > 0) { LVITEM item; item.mask = LVIF_STATE | LVIF_PARAM; item.iItem = (INT)wParam; item.iSubItem = 0; item.stateMask = LVIS_STATEIMAGEMASK; item.state = INDEXTOSTATEIMAGEMASK(RemovingItemState); // IMPORTANT: // We need to null the param. This is important because the user // will most likely be storing pointers to heap allocations in // here, and free the allocation after it has deleted the // item. The user may allocate sometime in the future and receive // the same pointer as is stored here. The user may call // LVM_FINDITEM or LVM_GETNEXTITEM and find this item, which // is supposed to be deleted. It may then attempt to delete // this item *twice*, which leads to bad things happening, // including *not* deleting the item that the user wanted to delete. item.lParam = (LPARAM)NULL; CallWindowProc(context->OldWndProc, hwnd, LVM_SETITEM, 0, (LPARAM)&item); { PH_TICK_ENTRY localEntry; PPH_TICK_ENTRY entry; PhpEnsureTickHashtableCreated(context); localEntry.Id = ListView_MapIndexToID(hwnd, (INT)wParam); entry = PhAddEntryHashtableEx(context->TickHashtable, &localEntry, NULL); entry->TickCount = GetTickCount(); } return TRUE; } else { // The item may still be under state highlighting. if (context->TickHashtable) { PH_TICK_ENTRY entry; entry.Id = ListView_MapIndexToID(hwnd, (INT)wParam); PhRemoveEntryHashtable(context->TickHashtable, &entry); } } } break; case LVM_GETITEM: { LVITEM item; ULONG itemState; ULONG oldMask; ULONG oldStateMask; if (!context->EnableState) break; // pass through memcpy(&item, (LPLVITEM)lParam, sizeof(LVITEM)); oldMask = item.mask; oldStateMask = item.stateMask; if (!(item.mask & LVIF_STATE)) { item.mask |= LVIF_STATE; item.stateMask = LVIS_STATEIMAGEMASK; } else { item.stateMask |= LVIS_STATEIMAGEMASK; } if (!CallWindowProc(oldWndProc, hwnd, LVM_GETITEM, 0, (LPARAM)&item)) return FALSE; // Check if the item is being deleted. If so, pretend it doesn't // exist. itemState = PH_GET_ITEM_STATE(item.state); if (itemState == RemovingItemState) return FALSE; item.mask = oldMask; item.stateMask = oldStateMask; item.state &= item.stateMask; memcpy((LPLVITEM)lParam, &item, sizeof(LVITEM)); } return TRUE; case ELVM_ADDFALLBACKCOLUMN: { if (context->NumberOfFallbackColumns < PH_MAX_COMPARE_FUNCTIONS) context->FallbackColumns[context->NumberOfFallbackColumns++] = (ULONG)wParam; else return FALSE; } return TRUE; case ELVM_ADDFALLBACKCOLUMNS: { ULONG numberOfColumns = (ULONG)wParam; PULONG columns = (PULONG)lParam; if (context->NumberOfFallbackColumns + numberOfColumns <= PH_MAX_COMPARE_FUNCTIONS) { memcpy( &context->FallbackColumns[context->NumberOfFallbackColumns], columns, numberOfColumns * sizeof(ULONG) ); context->NumberOfFallbackColumns += numberOfColumns; } else { return FALSE; } } return TRUE; case ELVM_ENABLESTATE: { context->EnableState = !!wParam; } return TRUE; case ELVM_INIT: { PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); // HACK to fix tooltips showing behind Always On Top windows. SetWindowPos(ListView_GetToolTips(hwnd), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); // Make sure focus rectangles are disabled. SendMessage(hwnd, WM_CHANGEUISTATE, MAKELONG(UIS_SET, UISF_HIDEFOCUS), 0); } return TRUE; case ELVM_SETCOLUMNWIDTH: { ULONG column = (ULONG)wParam; LONG width = (LONG)lParam; if (width == ELVSCW_AUTOSIZE_REMAININGSPACE) { RECT clientRect; LONG availableWidth; ULONG i; LVCOLUMN lvColumn; GetClientRect(hwnd, &clientRect); availableWidth = clientRect.right; i = 0; lvColumn.mask = LVCF_WIDTH; while (TRUE) { if (i != column) { if (CallWindowProc(oldWndProc, hwnd, LVM_GETCOLUMN, i, (LPARAM)&lvColumn)) { availableWidth -= lvColumn.cx; } else { break; } } i++; } if (availableWidth >= 40) return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, availableWidth); } return CallWindowProc(oldWndProc, hwnd, LVM_SETCOLUMNWIDTH, column, width); } break; case ELVM_SETCOMPAREFUNCTION: { ULONG column = (ULONG)wParam; PPH_COMPARE_FUNCTION compareFunction = (PPH_COMPARE_FUNCTION)lParam; if (column >= PH_MAX_COMPARE_FUNCTIONS) return FALSE; context->CompareFunctions[column] = compareFunction; } return TRUE; case ELVM_SETCONTEXT: { context->Context = (PVOID)lParam; } return TRUE; case ELVM_SETCURSOR: { context->Cursor = (HCURSOR)lParam; } return TRUE; case ELVM_SETHIGHLIGHTINGDURATION: { context->HighlightingDuration = (ULONG)wParam; } return TRUE; case ELVM_SETITEMCOLORFUNCTION: { context->ItemColorFunction = (PPH_EXTLV_GET_ITEM_COLOR)lParam; } return TRUE; case ELVM_SETITEMFONTFUNCTION: { context->ItemFontFunction = (PPH_EXTLV_GET_ITEM_FONT)lParam; } return TRUE; case ELVM_SETNEWCOLOR: { context->NewColor = (COLORREF)wParam; } return TRUE; case ELVM_SETREDRAW: { if (wParam) context->EnableRedraw++; else context->EnableRedraw--; if (context->EnableRedraw == 1) { SendMessage(hwnd, WM_SETREDRAW, TRUE, 0); InvalidateRect(hwnd, NULL, FALSE); } else if (context->EnableRedraw == 0) { SendMessage(hwnd, WM_SETREDRAW, FALSE, 0); } } return TRUE; case ELVM_SETREMOVINGCOLOR: { context->RemovingColor = (COLORREF)wParam; } return TRUE; case ELVM_SETSORT: { context->SortColumn = (ULONG)wParam; context->SortOrder = (PH_SORT_ORDER)lParam; PhSetHeaderSortIcon(ListView_GetHeader(hwnd), context->SortColumn, context->SortOrder); } return TRUE; case ELVM_SETSORTFAST: { context->SortFast = !!wParam; } return TRUE; case ELVM_SETSTATEHIGHLIGHTING: { if (wParam) context->EnableStateHighlighting++; else context->EnableStateHighlighting--; } return TRUE; case ELVM_SETTRISTATE: { context->TriState = !!wParam; } return TRUE; case ELVM_SETTRISTATECOMPAREFUNCTION: { context->TriStateCompareFunction = (PPH_COMPARE_FUNCTION)lParam; } return TRUE; case ELVM_SORTITEMS: { if (context->SortFast) { // This sort method is faster than the normal sort because our comparison function // doesn't have to call the list view window procedure to get the item lParam values. // The disadvantage of this method is that default sorting is not available - if a // column doesn't have a comparison function, it doesn't get sorted at all. ListView_SortItems( hwnd, PhpExtendedListViewCompareFastFunc, (LPARAM)context ); } else { ListView_SortItemsEx( hwnd, PhpExtendedListViewCompareFunc, (LPARAM)context ); } } return TRUE; case ELVM_TICK: { if ( context->EnableStateHighlighting > 0 && context->TickHashtable && context->TickHashtable->Count != 0 ) { PhListTick(context); } } return TRUE; } return CallWindowProc(oldWndProc, hwnd, uMsg, wParam, lParam); }