Что нового

[Данные, строки] Сортировка в ListView дат и чисел нестандартных форматов

n-deer

Новичок
Сообщения
16
Репутация
0
Подскажите, пожалуйста, есть ли возможность настроить корректную сортировку в ListView, в зависимости от типа и формата данных в колонке?

Конкретно интересуют такие колонки:

1. Дата в формате DD-MON-YY
Пример сортировки по возрастанию:
23-JUL-10
14-APR-11
19-MAY-11

2. Число в формате 12,345.67 (т.е. с запятой в качестве разделителя разрядов)

Пока приходит в голову только сделать рядом с ними скрытые колонки в "нормальном" формате и сортировать по ним, но может быть есть более правильные способы?

Пытался разобрать пример 2 к функции _GUICtrlListView_RegisterSortCallBack - он делает что-то похожее, но не смог понять как он работает и, соответственно, подстроить под свои нужды.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
n-deer
С датами у меня так работает:
Код:
#include <Date.au3>
#include <Array.au3>

Dim $aTo_Sort[8] = [7, '23-Jul-10', '16-MAR-09', '08-Sep-11', '14-MAY-11', '12-may-11', '01-May-11', '10-Oct-08']

$aSort = _Sort_Date_Array($aTo_Sort, 1);, 1)
If Not @error Then
	_ArrayDisplay($aSort)
Else
	MsgBox(16, 'Error', 'Error')
EndIf

Func _Sort_Date_Array($a_Array, $i_Start = 0, $i_Descending = 0, $s_Delimiters = '-')
	Local $a_Month[13] = [12, 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], _
			$i_Ubound, $aTemp[1][2], $aSplit, $s_Day, $s_Mon, $s_Year, $a_Return[1]
	If Not IsArray($a_Array) Then Return SetError(1)
	If UBound($a_Array, 2) Then Return SetError(1)
	$i_Ubound = UBound($a_Array)
	ReDim $aTemp[$i_Ubound][2]
	For $i = $i_Start To $i_Ubound - 1
		$aSplit = StringSplit($a_Array[$i], $s_Delimiters)
		If $aSplit[0] <> 3 Then Return SetError(1)
		$s_Day = $aSplit[1]
		$s_Mon = _ArraySearch($a_Month, $aSplit[2])
		If @error Then Return SetError(1)
		$s_Year = '20' & $aSplit[3]
		$aTemp[$i][0] = _DateToDayValue($s_Year, $s_Mon, $s_Day)
		If @error Then Return SetError(1)
		$aTemp[$i][1] = $a_Array[$i]
	Next
	_ArraySort($aTemp, $i_Descending, $i_Start)
	ReDim $a_Return[$i_Ubound]
	If $i_Start Then
		For $i = 0 To $i_Start - 1
			$a_Return[$i] = $a_Array[$i]
		Next
	EndIf
	For $i = $i_Start To $i_Ubound - 1
		$a_Return[$i] = $aTemp[$i][1]
	Next
	Return SetError(0, '', $a_Return)
EndFunc   ;==>_Sort_Date_Array



С числами:
Код:
#include <Array.au3>

Dim $aTo_Sort[7] = ['12,345.67', '13,645.54', '11,565.55', '33,333.33', '11,455.90', '22,111.99', '2,322.22']

Dim $aTemp[UBound($aTo_Sort)][2]
For $i = 0 To UBound($aTo_Sort) - 1
	$aTemp[$i][0] = Number(StringReplace($aTo_Sort[$i], ',', ''))
	$aTemp[$i][1] = $aTo_Sort[$i]
Next
_ArraySort($aTemp)
For $i = 0 To UBound($aTo_Sort) - 1
	$aTo_Sort[$i] = $aTemp[$i][1]
Next
_ArrayDisplay($aTo_Sort)
 
Автор
N

n-deer

Новичок
Сообщения
16
Репутация
0
madmasles [?]
С датами у меня так работает:

Я, наверно, не совсем точно описал суть задачи - необходимо реализовать сортировку ListView "на лету", по клику на заголовке столбца, а не предварительную сортировку массива данных. Функция _GUICtrlListView_SortItems с этим прекрасно справляется, но вот как ей объяснить, что некоторые колонки прежде чем сортировать нужно предварительно обработать?

AZJIO [?]
Зачем скрытые колонки, можно хранить массив данных. Сделать конвертор дат в цифру, сортировать массив по колонке цифр, потом колонками с датой в нужном формате заполнять ListView. Кстати, клик по заголовкам колонок можно получить через WM_NOTIFY и перезаполнить ListView.

Ну массив с данными, наверно, имеет смысл использовать, если планируется какой-то еще функционал на него вешать (например, фильтры), а если только для сортировки, то в моем случае, думаю, будет уже излишне - в списке много колонок, но из них всего 2 с нестандартными правилами сортировки, остальные текстовые. Если я правильно понимаю, массив ведь "отожрет" еще столько же памяти, сколько и сам ListView?

OffTopic:
Кстати, а можно ли как-то замерить сколько памяти выделено под массив или элемент GUI? А то, может быть, я совершенно напрасно морочусь этим.


Почему у меня, собственно говоря, возник такой вопрос и была создана тема. В упоминавшемся примере ведь как-то удалось обойтись без дополнительных сущностей (лишних колонок, массива), вот только я, хоть убей, не могу понять как...
Код:
Func _Example2()
    Local $hImage, $aIcons[3] = [0, 3, 6]
    Local $iExWindowStyle = BitOR($WS_EX_DLGMODALFRAME, $WS_EX_CLIENTEDGE)
    Local $iExListViewStyle = BitOR($LVS_EX_FULLROWSELECT, $LVS_EX_SUBITEMIMAGES, $LVS_EX_GRIDLINES, $LVS_EX_CHECKBOXES, $LVS_EX_DOUBLEBUFFER)

    GUICreate("ListView Sort Treat Numbers as Strings", 400, 200, 100)

    $hListView = GUICtrlCreateListView("Column1|Col2|Col3", 10, 10, 280, 180, -1, $iExWindowStyle)
    _GUICtrlListView_SetExtendedListViewStyle($hListView, $iExListViewStyle)

    ; Load images
    $hImage = _GUIImageList_Create(18, 18, 5, 3)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -7)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -12)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -3)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -4)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -5)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -6)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -9)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -10)
    _GUIImageList_AddIcon($hImage, @SystemDir & "\shell32.dll", -11)
    _GUICtrlListView_SetImageList($hListView, $hImage, 1)

    _AddRow($hListView, "ABC|000500|10.05.2004", $aIcons)
    _AddRow($hListView, "DEF|444|11.05.2005", $aIcons, 1)
    _AddRow($hListView, "CDE|555|12.05.2004", $aIcons, 2)

    GUISetState()

    GUICreate("ListView Sort Treat Numbers as Numbers", 400, 200, 400)

    $hListView2 = GUICtrlCreateListView("Column1|Col2|Col3", 10, 10, 280, 180, -1, $iExWindowStyle)
    _GUICtrlListView_SetExtendedListViewStyle($hListView2, $iExListViewStyle)
    _GUICtrlListView_SetImageList($hListView2, $hImage, 1)

    _AddRow($hListView2, "ABC|000500|10.05.2004", $aIcons)
    _AddRow($hListView2, "DEF|444|11.05.2005", $aIcons, 1)
    _AddRow($hListView2, "CDE|555|12.05.2004", $aIcons, 2)

    GUISetState()

    _GUICtrlListView_RegisterSortCallBack($hListView, False)
    _GUICtrlListView_RegisterSortCallBack($hListView2)
    GUIRegisterMsg($WM_NOTIFY, "_WM_NOTIFY")

    While 1
        Switch GUIGetMsg()
            Case $GUI_EVENT_CLOSE
                ExitLoop
        EndSwitch
    WEnd

    _GUICtrlListView_UnRegisterSortCallBack($hListView)
    _GUICtrlListView_UnRegisterSortCallBack($hListView2)
    GUIRegisterMsg($WM_NOTIFY, "")
EndFunc   ;==>_Example2

Func _AddRow($hWnd, $sItem, $aIcons, $iPlus = 0)
    Local $aItem = StringSplit($sItem, "|")
    Local $iIndex = _GUICtrlListView_AddItem($hWnd, $aItem[1], $aIcons[0] + $iPlus, _GUICtrlListView_GetItemCount($hWnd) + 9999)
    _GUICtrlListView_SetColumnWidth($hWnd, 0, $LVSCW_AUTOSIZE_USEHEADER)

    For $x = 2 To $aItem[0]
        _GUICtrlListView_AddSubItem($hWnd, $iIndex, $aItem[$x], $x - 1, $aIcons[$x - 1] + $iPlus)
        _GUICtrlListView_SetColumnWidth($hWnd, $x - 1, $LVSCW_AUTOSIZE)
    Next
EndFunc   ;==>_AddRow

Func _WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    #forceref $hWnd, $iMsg, $iwParam
    Local $hWndFrom, $iCode, $tNMHDR, $hWndListView, $hWndListView2

    $hWndListView = $hListView
    $hWndListView2 = $hListView2
    If Not IsHWnd($hListView) Then $hWndListView = GUICtrlGetHandle($hListView)
    If Not IsHWnd($hListView2) Then $hWndListView2 = GUICtrlGetHandle($hListView2)

    $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iCode = DllStructGetData($tNMHDR, "Code")

    Switch $hWndFrom
        Case $hWndListView, $hWndListView2
            Switch $iCode
                Case $LVN_COLUMNCLICK ; A column was clicked
                    Local $tInfo = DllStructCreate($tagNMLISTVIEW, $ilParam)

                    ; Kick off the sort callback
                    _GUICtrlListView_SortItems($hWndFrom, DllStructGetData($tInfo, "SubItem"))
                    ; No return value
            EndSwitch
    EndSwitch
    Return $__LISTVIEWCONSTANT_GUI_RUNDEFMSG
EndFunc   ;==>_WM_NOTIFY

P.S. А можно чуть развернутей о WM_NOTIFY - что это и с чем ее едят? В приведенном примере она тоже используется, но опять же непонятно с какой целью.
 

madmasles

Модератор
Глобальный модератор
Сообщения
7,790
Репутация
2,322
n-deer,
Код:
#include <Date.au3>
#include <Array.au3>
#include <GuiConstantsEx.au3>
#include <GuiListView.au3>
#include <ListviewConstants.au3>

$iDesc = 1

$aArray = _Get_Random_Array(1000)

GUICreate('Test', 400, 300)
$nListView = GUICtrlCreateListView('', 2, 2, 394, 268, $GUI_SS_DEFAULT_LISTVIEW, $LVS_EX_GRIDLINES)
$hListView = GUICtrlGetHandle($nListView)
_GUICtrlListView_AddColumn($hListView, 'Date', 250, 2)
GUISetState()
_GUICtrlListView_AddArray($hListView, $aArray)
$aArray = 0

While 1
	$nMsg = GUIGetMsg()
	Switch $nMsg
		Case $GUI_EVENT_CLOSE
			Exit
		Case $nListView
			GUICtrlSetState($nListView, $GUI_DISABLE)
			If $iDesc Then
				$iDesc = 0
				$sSort = '(+)  '
			Else
				$iDesc = 1
				$sSort = '(-)  '
			EndIf
			$iCount = _GUICtrlListView_GetItemCount($hListView)
			Dim $aArray[$iCount]
			For $i = 0 To $iCount - 1
				$aArray[$i] = _GUICtrlListView_GetItemText($hListView, $i)
			Next
			$aArray = _Sort_Date_Array($aArray, 0, $iDesc)
			If Not @error Then
				_GUICtrlListView_DeleteAllItems($hListView)
				_GUICtrlListView_AddArray($hListView, $aArray)
				_GUICtrlListView_SetColumn($hListView, 0, $sSort & 'Date', -1, 2)
				$aArray = 0
			EndIf
			GUICtrlSetState($nListView, $GUI_ENABLE)
	EndSwitch
WEnd

Func _Sort_Date_Array($a_Array, $i_Start = 0, $i_Descending = 0, $s_Delimiters = '-')
	Local $a_Month[13] = [12, 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], _
			$i_Ubound, $aTemp[1][2], $aSplit, $s_Day, $s_Mon, $s_Year, $a_Return[1][1]
	If Not IsArray($a_Array) Then Return SetError(1)
	If UBound($a_Array, 2) Then Return SetError(1)
	$i_Ubound = UBound($a_Array)
	ReDim $aTemp[$i_Ubound][2]
	For $i = $i_Start To $i_Ubound - 1
		$aSplit = StringSplit($a_Array[$i], $s_Delimiters)
		If $aSplit[0] <> 3 Then Return SetError(1)
		$s_Day = $aSplit[1]
		$s_Mon = _ArraySearch($a_Month, $aSplit[2])
		If @error Then Return SetError(1)
		$s_Year = '20' & $aSplit[3]
		$aTemp[$i][0] = _DateToDayValue($s_Year, $s_Mon, $s_Day)
		If @error Then Return SetError(1)
		$aTemp[$i][1] = $a_Array[$i]
	Next
	_ArraySort($aTemp, $i_Descending, $i_Start)
	ReDim $a_Return[$i_Ubound][1]
	If $i_Start Then
		For $i = 0 To $i_Start - 1
			$a_Return[$i][0] = $a_Array[$i]
		Next
	EndIf
	For $i = $i_Start To $i_Ubound - 1
		$a_Return[$i][0] = $aTemp[$i][1]
	Next

	Return SetError(0, '', $a_Return)
EndFunc   ;==>_Sort_Date_Array

Func _Get_Random_Array($i_Ubound)
	Local $a_Month[13] = [12, 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], _
			$aReturn[$i_Ubound][1]
	For $i = 0 To $i_Ubound - 1
		$aReturn[$i][0] = StringFormat('%02i-%s-%02i', Random(1, 28, 1), StringUpper($a_Month[Random(1, 12, 1)]), Random(1, 12, 1))
	Next
	Return $aReturn
EndFunc   ;==>_Get_Random_Array
 

kaster

Мой Аватар, он лучший самый
Команда форума
Глобальный модератор
Сообщения
4,020
Репутация
626
n-deer [?]
сколько памяти выделено под массив
пропорционально кол-ву элементов в массиве. если массив связан исключительно с контролами, то, конечно, не стоит даже и замарачиваться.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Таскать за собой массив с теми же данными, что и в ListView, бессмысленно. Да и к тому же, после сортировки массива придется полностью обновлять все данные в ListView. В API предусмотрен хороший механизм сортировки, вот его и нужно использовать. Вот пример на чистом AutoIt:

Код:
#Include <Date.au3>
#Include <GUIConstantsEx.au3>
#Include <ListViewConstants.au3>
#Include <StructureConstants.au3>
#Include <WinAPI.au3>
#Include <WindowsConstants.au3>

$hForm = GUICreate('MyGUI', 400, 400)
$LV = GUICtrlCreateListView('Number|Date', 10, 10, 380, 380)

$tFT = DllStructCreate('dword;dword')
For $i = 1 To 100
	DllStructSetData($tFT, 2, Random(29400000, 30150000, 1))
	GUICtrlCreateListViewItem(StringFormat('%.2f', Random(0, 9)) & '|' & StringRegExpReplace(_Date_Time_FileTimeToStr($tFT), '\s.[^\s]*\z', ''), $LV)
Next

GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
GUISetState()

While 1
	Switch GUIGetMsg()
		Case -3
			ExitLoop
	EndSwitch
WEnd

Func _SortProc($iItem1, $iItem2, $iSubItem)

	Local $Val[2], $Data

	For $i = 0 To 1
		$Data = StringSplit(GUICtrlRead(Eval('iItem' & ($i + 1))), '|', 2)
		$Val[$i] = $Data[$iSubItem]
	Next
	Switch $iSubItem
		Case 0 ; Float number
			For $i = 0 To 1
				$Val[$i] = Number($Val[$i])
			Next
			Select
				Case $Val[0] > $Val[1]
					Return 1
				Case $Val[0] < $Val[1]
					Return -1
				Case Else
					Return 0
			EndSelect
		Case 1 ; Date in mm/dd/yyyy
			For $i = 0 To 1
				$Data = StringSplit($Val[$i], '/', 2)
				$Val[$i] = _Date_Time_EncodeFileTime($Data[0], $Data[1], $Data[2])
			Next
			Return _Date_Time_CompareFileTime(DllStructGetPtr($Val[0]), DllStructGetPtr($Val[1]))
	EndSwitch
EndFunc   ;==>_SortProc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm

			Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
			Local $CtrlID = DllStructGetData($tNMHDR, 'IDFrom')
			Local $Code = DllStructGetData($tNMHDR, 'Code')

			Switch $CtrlID
				Case $LV
					Switch $Code
						Case $LVN_COLUMNCLICK

							Local $tNMLISTVIEW = DllStructCreate($tagNMLISTVIEW, $lParam)
							Local $SubItem = DllStructGetData($tNMLISTVIEW, 'SubItem')
							Local $hProc = DllCallbackRegister('_SortProc', 'int', 'lparam;lparam;lparam')
							Local $pProc = DllCallbackGetPtr($hProc)

							GUICtrlSendMsg($LV, $LVM_SORTITEMS, $SubItem, $pProc)
							DllCallbackFree($hProc)
					EndSwitch
			EndSwitch
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY


n-deer сказал(а):
А можно чуть развернутей о WM_NOTIFY - что это и с чем ее едят?

В данном случае для того, чтобы отловить нажатие на заголовке колонки в ListView и опеделить ее (колонки) номер.
 
Автор
N

n-deer

Новичок
Сообщения
16
Репутация
0
Yashied [?]
Таскать за собой массив с теми же данными, что и в ListView, бессмысленно. Да и к тому же, после сортировки массива придется полностью обновлять все данные в ListView. В API предусмотрен хороший механизм сортировки, вот его и нужно использовать. Вот пример на чистом AutoIt:

Не работает :(

Код:
Test4.au3 (33) : ==> Array variable subscript badly formatted.:
$Val[$i] = $Data[$iSubItem]
$Val[$i] = $Data[^ ERROR

$iSubItem передается в функцию равной -1 при клике на заголовок любой колонки.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
n-deer сказал(а):
$iSubItem передается в функцию равной -1 при клике на заголовок любой колонки.

У тебя x64? Тогда замени

Код:
$tNMLISTVIEW = DllStructCreate($tagNMLISTVIEW, $lParam)


на

Код:
$tNMLISTVIEW = DllStructCreate('hwnd hWndFrom;uint_ptr IDFrom;uint_ptr Code;int Item;int SubItem;uint NewState;uint OldState;uint Changed;int X;int Y;lparam Param', $lParam)


Это бага в AutoIt (StructureConstants.au3).

CreatoR сказал(а):
Хороший пример, но он сортирует только в одну сторону.

Если функция _SortProc() возвращает положительное число, то сортирует в одну сторону, отрицательное - в другую, 0 - два элемента равны. Я лишь предоставил простой пример...
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Вот пример с сортировкой в обе стороны (как в Windows Explorer'е).

Код:
#Include <Date.au3>
#Include <GUIConstantsEx.au3>
#Include <GUIListView.au3>
#Include <GUIHeader.au3>
#Include <StructureConstants.au3>
#Include <WinAPI.au3>
#Include <WindowsConstants.au3>

$hProc = DllCallbackRegister('_SortProc', 'int', 'lparam;lparam;lparam')
$pProc = DllCallbackGetPtr($hProc)
$Col = 0

$hForm = GUICreate('MyGUI', 400, 400)
$LV = GUICtrlCreateListView('Number|Date', 10, 10, 380, 380)
$hHdr = _GUICtrlListView_GetHeader(-1)
For $i = 0 To 1
	_GUICtrlHeader_SetItemParam($hHdr, $i, 1)
Next

$tFT = DllStructCreate('dword;dword')
For $i = 1 To 100
	DllStructSetData($tFT, 2, Random(29400000, 30150000, 1))
	GUICtrlCreateListViewItem(StringFormat('%.2f', Random(0, 9)) & '|' & StringRegExpReplace(_Date_Time_FileTimeToStr($tFT), '\s.[^\s]*\z', ''), $LV)
Next

GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
_Sort($LV, $Col)
GUISetState()

While 1
	Switch GUIGetMsg()
		Case -3
			ExitLoop
	EndSwitch
WEnd

DllCallbackFree($hProc)

Func _Sort($CtrlID, $iSubItem)
	Return GUICtrlSendMsg($CtrlID, $LVM_SORTITEMS, $iSubItem, $pProc)
EndFunc   ;==>_Sort

Func _SortProc($iItem1, $iItem2, $iSubItem)

	Local $Val[2], $Data

	For $i = 0 To 1
		$Data = StringSplit(GUICtrlRead(Eval('iItem' & ($i + 1))), '|', 2)
		$Val[$i] = $Data[$iSubItem]
	Next
	Switch $iSubItem
		Case 0 ; Float number
			For $i = 0 To 1
				$Val[$i] = Number($Val[$i])
			Next
			Select
				Case $Val[0] > $Val[1]
					Return  _GUICtrlHeader_GetItemParam($hHdr, $iSubItem)
				Case $Val[0] < $Val[1]
					Return -_GUICtrlHeader_GetItemParam($hHdr, $iSubItem)
				Case Else
					Return 0
			EndSelect
		Case 1 ; Date in mm/dd/yyyy
			For $i = 0 To 1
				$Data = StringSplit($Val[$i], '/', 2)
				$Val[$i] = _Date_Time_EncodeFileTime($Data[0], $Data[1], $Data[2])
			Next
			Return -_GUICtrlHeader_GetItemParam($hHdr, $iSubItem) * _Date_Time_CompareFileTime(DllStructGetPtr($Val[0]), DllStructGetPtr($Val[1]))
	EndSwitch
EndFunc   ;==>_SortProc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm

			Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
			Local $CtrlID = DllStructGetData($tNMHDR, 'IDFrom')
			Local $Code = DllStructGetData($tNMHDR, 'Code')

			Switch $CtrlID
				Case $LV
					Switch $Code
						Case $LVN_COLUMNCLICK

							Local $tNMLISTVIEW = DllStructCreate('hwnd hWndFrom;uint_ptr IDFrom;uint_ptr Code;int Item;int SubItem;uint NewState;uint OldState;uint Changed;int X;int Y;lparam Param', $lParam)
							Local $SubItem = DllStructGetData($tNMLISTVIEW, 'SubItem')

							If ($Col = $SubItem) Then
								_GUICtrlHeader_SetItemParam($hHdr, $SubItem, -_GUICtrlHeader_GetItemParam($hHdr, $SubItem))
							Else
								$Col = $SubItem
							EndIf
							If Not _Sort($LV, $SubItem) Then
								ConsoleWrite('Sorting error!' & @CR)
							EndIf
					EndSwitch
			EndSwitch
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY
 
Автор
N

n-deer

Новичок
Сообщения
16
Репутация
0

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
n-deer сказал(а):
Этот вариант универсальный или нужно проверять x86 / x64?

Да, универсальный.

P.S

Вообще, структура NMHDR, которая используется в куче других структур, должна выглядеть следующим образом:

Код:
$tagNMHDR = "hwnd hWndFrom;uint_ptr IDFrom;uint_ptr Code"


В этом случае все последующие структуры будут автоматически выровнены вне зависимости от разрядности системы. Но есть одна проблема: значение "Code" в этом случае будет положительным (вообщем-то, так оно и должно быть), но почему-то во всех UDF все соответствующие константы определены как отрицательные числа. Поэтому просто исправить $tagNMHDR в StructureConstants.au3 нельзя, нужно исправлять все константы во всех *Constants.au3 файлах. Кроме того, я нашел еще несколько неверно написанных структур, которые не будут работать в x64, например NMTREEVIEW для x64 должна выглядеть так:

Код:
$tagNMTREEVIEW = $tagNMHDR & ";uint Aligment1;uint Action;uint Aligment2;uint OldMask;ptr OldhItem;uint OldState;uint OldStateMask;ptr OldText;int OldTextMax;int OldImage;int OldSelectedImage;int OldChildren;lparam OldParam;uint Aligment3;uint NewMask;ptr NewhItem;uint NewState;uint NewStateMask;ptr NewText;int NewTextMax;int NewImage;int NewSelectedImage;int NewChildren;lparam NewParam;int X; int Y"
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
n-deer

Если ты заметил, то скорость сортировки второго столбца заметно ниже первого. Поэтому, я все же рекомендую хранить данные о дате и времени в отдельном массиве в виде FILETIME структур (обычное 64-разрядное число), и в функции _SortProc() сравнивать именно эти числа, а не обрабатывать постоянно для каждого элемента дату. В следующем примере скорость сортировки должна быть в несколько раз выше. А кроме того, упростился сам код.

Код:
#Include <Date.au3>
#Include <GUIConstantsEx.au3>
#Include <GUIListView.au3>
#Include <GUIHeader.au3>
#Include <StructureConstants.au3>
#Include <WinAPI.au3>
#Include <WindowsConstants.au3>

$hProc = DllCallbackRegister('_SortProc', 'int', 'lparam;lparam;lparam')
$pProc = DllCallbackGetPtr($hProc)
$Col = 0

$hForm = GUICreate('MyGUI', 400, 400)
$LV = GUICtrlCreateListView('Number|Date', 10, 10, 380, 380)
$hHdr = _GUICtrlListView_GetHeader(-1)
For $i = 0 To 1
	_GUICtrlHeader_SetItemParam($hHdr, $i, 1)
Next

Dim $FT[100][3]

$tFT = DllStructCreate('dword;dword')
$tDT = DllStructCreate('uint64', DllStructGetPtr($tFT))
For $i = 0 To UBound($FT) - 1
	DllStructSetData($tFT, 2, Random(29400000, 30150000, 1))
	$FT[$i][0] = StringFormat('%.2f', Random(0, 9))
	$FT[$i][1] = DllStructGetData($tDT, 1)
	$FT[$i][2] = GUICtrlCreateListViewItem($FT[$i][0] & '|' & StringRegExpReplace(_Date_Time_FileTimeToStr($tFT), '\s.[^\s]*\z', ''), $LV)
Next

GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
_Sort($LV, $Col)
GUISetState()

While 1
	Switch GUIGetMsg()
		Case -3
			ExitLoop
	EndSwitch
WEnd

DllCallbackFree($hProc)

Func _Sort($CtrlID, $iSubItem)
	Return GUICtrlSendMsg($CtrlID, $LVM_SORTITEMS, $iSubItem, $pProc)
EndFunc   ;==>_Sort

Func _SortProc($iItem1, $iItem2, $iSubItem)
	Select
		Case $FT[$iItem1 - $FT[0][2]][$iSubItem] > $FT[$iItem2 - $FT[0][2]][$iSubItem]
			Return  _GUICtrlHeader_GetItemParam($hHdr, $iSubItem)
		Case $FT[$iItem1 - $FT[0][2]][$iSubItem] < $FT[$iItem2 - $FT[0][2]][$iSubItem]
			Return -_GUICtrlHeader_GetItemParam($hHdr, $iSubItem)
		Case Else
			Return 0
	EndSelect
EndFunc   ;==>_SortProc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm

			Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
			Local $CtrlID = DllStructGetData($tNMHDR, 'IDFrom')
			Local $Code = DllStructGetData($tNMHDR, 'Code')

			Switch $CtrlID
				Case $LV
					Switch $Code
						Case $LVN_COLUMNCLICK

							Local $tNMLISTVIEW = DllStructCreate('hwnd hWndFrom;uint_ptr IDFrom;uint_ptr Code;int Item;int SubItem;uint NewState;uint OldState;uint Changed;int X;int Y;lparam Param', $lParam)
							Local $SubItem = DllStructGetData($tNMLISTVIEW, 'SubItem')

							If ($Col = $SubItem) Then
								_GUICtrlHeader_SetItemParam($hHdr, $SubItem, -_GUICtrlHeader_GetItemParam($hHdr, $SubItem))
							Else
								$Col = $SubItem
							EndIf
							If Not _Sort($LV, $SubItem) Then
								ConsoleWrite('Sorting error!' & @CR)
							EndIf
					EndSwitch
			EndSwitch
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY


Тоже самое, но со "стрелочками".

Код:
#Include <Date.au3>
#Include <GUIConstantsEx.au3>
#Include <GUIListView.au3>
#Include <GUIHeader.au3>
#Include <StructureConstants.au3>
#Include <WinAPI.au3>
#Include <WindowsConstants.au3>

$hProc = DllCallbackRegister('_SortProc', 'int', 'lparam;lparam;lparam')
$pProc = DllCallbackGetPtr($hProc)

$hForm = GUICreate('MyGUI', 400, 400)
$LV = GUICtrlCreateListView('Number|Date', 10, 10, 380, 380)
$hHdr = _GUICtrlListView_GetHeader(-1)

Dim $FT[100][3]

$tFT = DllStructCreate('dword;dword')
$tDT = DllStructCreate('uint64', DllStructGetPtr($tFT))
For $i = 0 To UBound($FT) - 1
	DllStructSetData($tFT, 2, Random(29400000, 30150000, 1))
	$FT[$i][0] = StringFormat('%.2f', Random(0, 9))
	$FT[$i][1] = DllStructGetData($tDT, 1)
	$FT[$i][2] = GUICtrlCreateListViewItem($FT[$i][0] & '|' & StringRegExpReplace(_Date_Time_FileTimeToStr($tFT), '\s.[^\s]*\z', ''), $LV)
Next

_GUICtrlHeader_SetItemFormat($hHdr, 0, BitOR(_GUICtrlHeader_GetItemFormat($hHdr, 0), $HDF_SORTUP))
_Sort($LV, 0)

GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
GUISetState()

While 1
	Switch GUIGetMsg()
		Case -3
			ExitLoop
	EndSwitch
WEnd

DllCallbackFree($hProc)

Func _Sort($CtrlID, $iSubItem)
	Return GUICtrlSendMsg($CtrlID, $LVM_SORTITEMS, $iSubItem, $pProc)
EndFunc   ;==>_Sort

Func _SortProc($iItem1, $iItem2, $iSubItem)

	Local $Format = _GUICtrlHeader_GetItemFormat($hHdr, $iSubItem)
	Local $Order

	Select
		Case BitAND($Format, $HDF_SORTDOWN)
			$Order =-1
		Case BitAND($Format, $HDF_SORTUP)
			$Order = 1
		Case Else
			Return 0
	EndSelect
	Select
		Case $FT[$iItem1 - $FT[0][2]][$iSubItem] > $FT[$iItem2 - $FT[0][2]][$iSubItem]
			Return  $Order
		Case $FT[$iItem1 - $FT[0][2]][$iSubItem] < $FT[$iItem2 - $FT[0][2]][$iSubItem]
			Return -$Order
		Case Else
			Return 0
	EndSelect
EndFunc   ;==>_SortProc

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)
	Switch $hWnd
		Case $hForm

			Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
			Local $CtrlID = DllStructGetData($tNMHDR, 'IDFrom')
			Local $Code = DllStructGetData($tNMHDR, 'Code')

			Switch $CtrlID
				Case $LV
					Switch $Code
						Case $LVN_COLUMNCLICK

							Local $tNMLISTVIEW = DllStructCreate('hwnd hWndFrom;uint_ptr IDFrom;uint_ptr Code;int Item;int SubItem;uint NewState;uint OldState;uint Changed;int X;int Y;lparam Param', $lParam)
							Local $SubItem = DllStructGetData($tNMLISTVIEW, 'SubItem')
							Local $Format = _GUICtrlHeader_GetItemFormat($hHdr, $SubItem)

							If Not BitAND($Format, BitOR($HDF_SORTDOWN, $HDF_SORTUP)) Then
								For $i = 0 To 1
									_GUICtrlHeader_SetItemFormat($hHdr, $i, BitAND(_GUICtrlHeader_GetItemFormat($hHdr, $i), BitNOT(BitOR($HDF_SORTDOWN, $HDF_SORTUP))))
								Next
								Switch $SubItem
									Case 0 ; Number
										_GUICtrlHeader_SetItemFormat($hHdr, $SubItem, BitOR(BitAND($Format, BitNOT(BitOR($HDF_SORTDOWN, $HDF_SORTUP))), $HDF_SORTUP))
									Case 1 ; Date
										_GUICtrlHeader_SetItemFormat($hHdr, $SubItem, BitOR(BitAND($Format, BitNOT(BitOR($HDF_SORTDOWN, $HDF_SORTUP))), $HDF_SORTDOWN))
								EndSwitch
							Else
								Select
									Case BitAND($Format, $HDF_SORTDOWN)
										_GUICtrlHeader_SetItemFormat($hHdr, $SubItem, BitOR(BitAND($Format, BitNOT($HDF_SORTDOWN)), $HDF_SORTUP))
									Case BitAND($Format, $HDF_SORTUP)
										_GUICtrlHeader_SetItemFormat($hHdr, $SubItem, BitOR(BitAND($Format, BitNOT($HDF_SORTUP)), $HDF_SORTDOWN))
								EndSelect
							EndIf
							_Sort($LV, $SubItem)
					EndSwitch
			EndSwitch
	EndSwitch
	Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY
 
Автор
N

n-deer

Новичок
Сообщения
16
Репутация
0
Yashied [?]
Если ты заметил, то скорость сортировки второго столбца заметно ниже первого. Поэтому, я все же рекомендую хранить данные о дате и времени в отдельном массиве в виде FILETIME структур (обычное 64-разрядное число), и в функции _SortProc() сравнивать именно эти числа, а не обрабатывать постоянно для каждого элемента дату. В следующем примере скорость сортировки должна быть в несколько раз выше. А кроме того, упростился сам код.

Да, действительно теперь в разы быстрее происходит сортировка. Спасибо! И отдельно за стрелочки :smile:

Пытаюсь сейчас понять как использовать структуры, но справка в этом отношении весьма лаконична... Видимо, подразумевается, что это и так всем известно, но для меня, если честно, это темный лес. Можешь пояснить - каким образом можно расширить данный ListView? Например, добавить еще одну колонку с датами и колонку с текстовыми данными.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
По поводу структур почитай здесь (если сразу тема не появится, обнови страницу), а про ListView - в справке.

Код:
GUICtrlCreateListViewItem("Col1|Col2|Col3|Col4", $LV)


Здесь будет 4 колонки, а все данные в ListView находятся, естественно, в текстовом виде.
 
Автор
N

n-deer

Новичок
Сообщения
16
Репутация
0
Yashied [?]
По поводу структур почитай здесь (если сразу тема не появится, обнови страницу), а про ListView - в справке.

Отличная статья! Я уже полез искать C/C++ мануалы, чтобы разобраться, а тут все под боком оказывается есть :smile:
Как-то я умудрился не заметить ее в поиске, наверно название темы сбило с толку.

Здесь будет 4 колонки, а все данные в ListView находятся, естественно, в текстовом виде.

Я не это имел в виду, просто мне вчера на сонную голову показалось, что структуры являются чем-то вроде ссылок на данные из ListView. Теперь проспавшись и прочитав твою статью, все встало на свои места. Спасибо за очень качественную помощь!
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
А какая причина использовать _GUICtrlListView_Create() вместо GUICtrlCreateListView()?
 

The Dream

Новичок
Сообщения
393
Репутация
3
Пробывал и ...не получилось..

Мне кажется,одна из причин - в "нативе" , колонки указываются в таком порядке = ..|..|..

Хотя опять боюсь,Вас достану.. :-\
 
Верх