Leaderboard
Popular Content
Showing content with the highest reputation on 12/01/2015 in all areas
-
Multi-line items in custom drawn ListView
pixelsearch and one other reacted to LarsJ for a topic
Items in a standard listview in details/report view (this example deals only with listviews in details/report view) can display a single line of text. There seems not to be any options to change this. There is no word wrap option. If you search the forums, it's possible to find examples of listviews with multiple lines of text in each row. The multi-line items are implemented as owner drawn items through LVS_OWNERDRAWFIXED control style and WM_DRAWITEM messages. A problem with the owner drawn technique is that you are forced to draw everything yourself. Besides item texts and the background behind texts (white for non-selected items, dark blue for selected items with focus, button face for selected items without focus) you also have to draw checkboxes, images, icons and the background behind these elements yourself. Another technique is custom drawn listview items. Custom drawn items are implemented through NM_CUSTOMDRAW notifications included in WM_NOTIFY messages. NM_CUSTOMDRAW notifications are generated automatically by the code in ComCtl32.dll when the listview is updated. Implementing custom drawn items is a matter of responding to these messages or not. The great advantage of custom drawn items is that the drawing process is divided into several stages. For a listview up to six different stages. Some of these stages can be used for default drawing without any additional code at all. Other stages can be used for custom drawing with your own code. Multi-line text items fits perfectly with the custom drawn technique. Item texts and the background is drawn by custom code. Checkboxes, images, icons and the background is drawn by default code. Increase height of listview items The usual way to increase the height of listview items is to respond to WM_MEASUREITEM messages. But this method can only be used for owner drawn listviews. In a custom drawn listview the height can be increased by defining a text font with a suitable height: Func _GUICtrlListView_SetItemHeightByFont( $hListView, $iHeight ) ; Get font of ListView control ; Copied from _GUICtrlGetFont example by KaFu ; See https://www.autoitscript.com/forum/index.php?showtopic=124526 Local $hDC = _WinAPI_GetDC( $hListView ), $hFont = _SendMessage( $hListView, $WM_GETFONT ) Local $hObject = _WinAPI_SelectObject( $hDC, $hFont ), $lvLOGFONT = DllStructCreate( $tagLOGFONT ) _WinAPI_GetObject( $hFont, DllStructGetSize( $lvLOGFONT ), DllStructGetPtr( $lvLOGFONT ) ) Local $hLVfont = _WinAPI_CreateFontIndirect( $lvLOGFONT ) ; Original ListView font _WinAPI_SelectObject( $hDC, $hObject ) _WinAPI_ReleaseDC( $hListView, $hDC ) _WinAPI_DeleteObject( $hFont ) ; Set height of ListView items by applying text font with suitable height $hFont = _WinAPI_CreateFont( $iHeight, 0 ) _WinAPI_SetFont( $hListView, $hFont ) _WinAPI_DeleteObject( $hFont ) ; Restore font of Header control Local $hHeader = _GUICtrlListView_GetHeader( $hListView ) If $hHeader Then _WinAPI_SetFont( $hHeader, $hLVfont ) ; Return original ListView font Return $hLVfont EndFunc Large images will also increase the height of listview items. See example E. Height of listview If the height of the listview does not fit an integer number of rows, you can see empty space below last row in the bottom of the listview. This issue is exacerbated by tall items. The following function is used to calculate the height of the listview to match a given number of rows: Func _GUICtrlListView_GetHeightToFitRows( $hListView, $iRows ) ; Get height of Header control Local $tRect = _WinAPI_GetClientRect( $hListView ) Local $hHeader = _GUICtrlListView_GetHeader( $hListView ) Local $tWindowPos = _GUICtrlHeader_Layout( $hHeader, $tRect ) Local $iHdrHeight = DllStructGetData( $tWindowPos , "CY" ) ; Get height of ListView item 0 (item 0 must exist) Local $aItemRect = _GUICtrlListView_GetItemRect( $hListView, 0, 0 ) ; Return height of ListView to fit $iRows items ; Including Header height and 8 pixels of additional room Return ( $aItemRect[3] - $aItemRect[1] ) * $iRows + $iHdrHeight + 8 EndFunc The calculation includes the height of the header. This means that the function works for a multi-line header with tall items (example A and B). Reference example WM_NOTIFY messages and NM_CUSTOMDRAW notifications are send to the parent of the listview control. The parent is the AutoIt GUI and messages can be handled by a function registered with GUIRegisterMsg. Example 1 is a reference example which shows the different stages of the custom drawing process. None of the stages contains any code except for ConsoleWrite statements. This is code for the reference example: #include <GUIConstants.au3> #include <GuiListView.au3> #include "GuiListViewEx.au3" Opt( "MustDeclareVars", 1 ) Global $hGui, $idListView, $hListView, $fListViewHasFocus = 0, $iItems = 3, $bAutoItMsgLoop = False Example() Func Example() ; Create GUI $hGui = GUICreate( "Custom draw stages", 420, 200 ) ; Create ListView $idListView = GUICtrlCreateListView( "", 10, 10, 400, 180, $GUI_SS_DEFAULT_LISTVIEW-$LVS_SINGLESEL, $WS_EX_CLIENTEDGE+$LVS_EX_FULLROWSELECT+$LVS_EX_GRIDLINES ) $hListView = GUICtrlGetHandle( $idListView ) ; Add columns to ListView _GUICtrlListView_AddColumn( $hListView, "Column 1", 94 ) _GUICtrlListView_AddColumn( $hListView, "Column 2", 94 ) _GUICtrlListView_AddColumn( $hListView, "Column 3", 94 ) _GUICtrlListView_AddColumn( $hListView, "Column 4", 94 ) ; Fill ListView For $i = 0 To $iItems - 1 GUICtrlCreateListViewItem( $i & "/Column 1|" & $i & "/Column 2|" & $i & "/Column 3|" & $i & "/Column 4", $idListView ) Next ; Adjust height of GUI and ListView to fit ten rows Local $iLvHeight = _GUICtrlListView_GetHeightToFitRows( $hListView, 10 ) WinMove( $hGui, "", Default, Default, Default, WinGetPos( $hGui )[3] - WinGetClientSize( $hGui )[1] + $iLvHeight + 20 ) WinMove( $hListView, "", Default, Default, Default, $iLvHeight ) ; Register WM_NOTIFY message handler ; To handle NM_CUSTOMDRAW notifications ; And to check when ListView receives/loses focus GUIRegisterMsg( $WM_NOTIFY, "WM_NOTIFY" ) ; Register WM_ACTIVATE message handler ; If GUI loses focus selected listview items are drawn with a button face background color. ; To check when GUI receives/loses focus ; When GUI receives focus selected items are redrawn with the dark blue background color. GUIRegisterMsg( $WM_ACTIVATE, "WM_ACTIVATE" ) ; Detection of received focus is faster through the WM_ACTIVATE message than directly ; through the listview. This provides a faster and smoother redraw of selected items. ; Show GUI GUISetState( @SW_SHOW ) ; Message loop While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop EndSwitch If Not $bAutoItMsgLoop Then _ ; We want to see only one message at a time $bAutoItMsgLoop = ( ConsoleWrite( "AutoIt message loop <<<<<<<<<<<<<<<<<<<<<" & @CRLF ) > 0 ) WEnd ; Cleanup GUIDelete() EndFunc ; WM_NOTIFY message handler Func WM_NOTIFY( $hWnd, $iMsg, $wParam, $lParam ) #forceref $hWnd, $iMsg, $wParam Local $tNMHDR = DllStructCreate( $tagNMHDR, $lParam ) Local $hWndFrom = HWnd( DllStructGetData( $tNMHDR, "hWndFrom" ) ) Local $iCode = DllStructGetData( $tNMHDR, "Code" ) Switch $hWndFrom Case $hListView Switch $iCode Case $NM_CUSTOMDRAW $bAutoItMsgLoop = False Local $tNMLVCustomDraw = DllStructCreate( $tagNMLVCUSTOMDRAW, $lParam ) Local $dwDrawStage = DllStructGetData( $tNMLVCustomDraw, "dwDrawStage" ) Switch $dwDrawStage ; Specifies the drawing stage ; Stage 1 Case $CDDS_PREPAINT ; Before the paint cycle begins ConsoleWrite( "Stage 1: CDDS_PREPAINT" & @CRLF ) Return $CDRF_NOTIFYITEMDRAW + _ ; Stage 2 will be carried out $CDRF_NOTIFYPOSTPAINT ; Stage 6 will be carried out Return $CDRF_NOTIFYITEMDRAW ; Notify the parent window before an item is painted Return $CDRF_NOTIFYPOSTPAINT ; Notify the parent window after the paint cycle is complete ; Stage 2 Case $CDDS_ITEMPREPAINT ; Before an item is painted ConsoleWrite( "Stage 2: CDDS_ITEMPREPAINT" & @CRLF ) Return $CDRF_NOTIFYSUBITEMDRAW + _ ; Stage 3 will be carried out $CDRF_NOTIFYPOSTPAINT ; Stage 5 will be carried out Return $CDRF_NOTIFYSUBITEMDRAW ; Notify the parent window before a subitem is painted Return $CDRF_NOTIFYPOSTPAINT ; Notify the parent window after an item is painted ; Stage 3 Case BitOR( $CDDS_ITEMPREPAINT, _ $CDDS_SUBITEM ) ; Before a subitem is painted ConsoleWrite( "Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM" & @CRLF ) Return $CDRF_NOTIFYPOSTPAINT ; Stage 4 will be carried out Return $CDRF_NOTIFYPOSTPAINT ; Notify the parent window after a subitem is painted ; Stage 4 Case BitOR( $CDDS_ITEMPOSTPAINT, _ $CDDS_SUBITEM ) ; After a subitem has been painted ConsoleWrite( "Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM" & @CRLF ) ; Stage 5 Case $CDDS_ITEMPOSTPAINT ; After an item has been painted ConsoleWrite( "Stage 5: CDDS_ITEMPOSTPAINT" & @CRLF ) ; Stage 6 Case $CDDS_POSTPAINT ; After the paint cycle is complete ConsoleWrite( "Stage 6: CDDS_POSTPAINT" & @CRLF ) EndSwitch Case $NM_KILLFOCUS If $fListViewHasFocus Then GUICtrlSendMsg( $idListView, $LVM_REDRAWITEMS, 0, $iItems - 1 ) $fListViewHasFocus = 0 EndIf Case $NM_SETFOCUS If Not $fListViewHasFocus Then _ GUICtrlSendMsg( $idListView, $LVM_REDRAWITEMS, 0, $iItems - 1 ) $fListViewHasFocus = 2 EndSwitch EndSwitch Return $GUI_RUNDEFMSG EndFunc ; WM_ACTIVATE message handler Func WM_ACTIVATE( $hWnd, $iMsg, $wParam, $lParam ) #forceref $iMsg, $lParam If $hWnd = $hGui Then _ $fListViewHasFocus = BitAND( $wParam, 0xFFFF ) ? 1 : 0 Return $GUI_RUNDEFMSG EndFunc Code is added to check when GUI and listview receives and loses focus. This is important in the other examples. Output in SciTE console immediately after example is opened: Stage 1: CDDS_PREPAINT Stage 2: CDDS_ITEMPREPAINT Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 5: CDDS_ITEMPOSTPAINT Stage 2: CDDS_ITEMPREPAINT Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 5: CDDS_ITEMPOSTPAINT Stage 2: CDDS_ITEMPREPAINT Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 3: CDDS_ITEMPREPAINT, CDDS_SUBITEM Stage 4: CDDS_ITEMPOSTPAINT, CDDS_SUBITEM Stage 5: CDDS_ITEMPOSTPAINT Stage 6: CDDS_POSTPAINT AutoIt message loop <<<<<<<<<<<<<<<<<<<<< Note that the entire custom draw process from stage 1 to 6 is not interrupted by the AutoIt message loop. The other examples are all based on the reference example. Examples This is common to all examples. First line in item texts is stored directly in the listview. Additional lines are stored in a global array named $aLines. Index in the array is item ID as returned by GUICtrlCreateListViewItem and stored in ItemParam internally in listview memory. Item texts and background is drawn with custom code. Other item elements and background is drawn with default code. In all examples LVS_SINGLESEL style is removed to be able to select multiple items. This is a picture of example E: Example 2, 3 and 4 are simple examples. Example 5 and 6 deals with subitem icons and colors. Example 7 about listview notifications shows a way to catch double click and Enter key. A dummy control is used to forward the double click event to AutoIt main message loop to avoid lengthy or blocking code in WM_NOTIFY function. Example 8 and 9 shows how to respond to header notifications and how to rearrange columns by dragging header items with the mouse. In both examples LVS_EX_HEADERDRAGDROP extended style is added to the listview. When columns are rearranged, header item index and listview subitem index is always the same independent of column position, while header item order changes depending on column position. Because the header is a child control of the listview, the listview must be subclassed to catch header notifications. Subclassing is implemented with the four functions SetWindowSubclass, GetWindowSubclass, RemoveWindowSubclass and DefSubclassProc (all implemented in WinAPIShellEx.au3). Since we are subclassing a header control contained in a listview this issue must be taking into account. Note that the subclass callback function is only running while the primary mouse button is pressed on the header. This means no performance impact on the listview eg. when you are dragging the scroll bar. This is important for a custom drawn listview. Quite a lot of extra code is added (most easily seen in example 9) to fix an issue due to column 0 and other columns have different left margins. When first column is moved to another position there is a white gap between columns for selected rows (Windows XP), or the text is painted too close to the left edge of the item (Windows 7). The problem is seen in the picture to the right where the two first columns are swapped: LVS_EX_HEADERDRAGDROP style is only used in example 8 and 9. Usage of a multi-line header is demonstrated in example A and B. See Custom/owner drawn multi-line header in ListView for more information. Example C shows a method to deal with focus issues when more controls (here just a single button) are added to the GUI. When the listview has focus selected items are drawn with the dark blue background color. The problem arises if the listview and GUI loses focus eg. to Calculator. When focus is lost selected items are drawn with the button face background color. If GUI receives focus again by clicking the button (and not the listview), selected items are first very briefly redrawn with the dark blue background color (it seems like a blink) and then with the correct button face background color. To avoid this issue a hidden label control is added to the GUI. Immediately before the GUI loses focus, focus is moved from the listview to the label. In example D items are added with the commands _GUICtrlListView_AddItem and _GUICtrlListView_AddSubItem. In all examples an array is used to store the multi-line item texts. The array contains all lines except the first line which is stored directly in the listview. This example shows how to manually store array row index in ItemParam when items are added with _GUICtrlListView_AddItem and _GUICtrlListView_AddSubItem. Inspiration for example E about large images in first column (see picture above) comes from this thread. In example E the images are used to increase the height of the listview items instead of a text font. Because it's large 128x128 pixel images there is plenty of room in subitems in second and third column. In all examples 15 lines of code is used to repaint the first line item text. The text that was painted by default code in middle of the item is first deleted by filling the item with the background color. Then the text is extracted from the listview and repainted in top of item. This code can be avoided by storing all text lines in the array. This is demonstrated in example F. This makes the custom draw code faster. Performance considerations In a custom drawn (or owner drawn or virtual) listview performance considerations are important because the custom drawing (or owner drawing or data display) is performed by AutoIt code. In a normal listview drawing and data display is performed by compiled C++ code in ComCtl32.dll. Lengthy and slow code in NM_CUSTOMDRAW Case statements (or WM_DRAWITEM functions or LVN_GETDISPINFO Case statements) should be avoided. Perform as many calculations as possible before the repetitive and fast executions of these code blocks. Use static variables to avoid repeating the same calculation again and again. Executing a function directly with DllCall or GUICtrlSendMsg is faster than executing the same function through an implementation in an UDF. Simple GDI functions are faster than more advanced GDI+ functions. Use different drawing stages to optimize custom drawing. The CDDS_PREPAINT stage is only performed once for the entire drawing process. The CDDS_ITEMPREPAINT stage is performed once per item. The stage given by BitOR( CDDS_ITEMPREPAINT, CDDS_SUBITEM ) is performed once per subitem including subitem 0. Default drawing should be used as much as possible, because the code is running in ComCtl32.dll. In a listview the time it takes to update all visible rows is proportional to the number of visible rows. Reducing the height of the list view and thus the number of visible rows improves performance. Especially in a custom drawn (or owner drawn or virtual) listview. ListviewMultilineItems.7z 1) Custom draw stages.au3 2) Two-line listview items.au3 3) Three-line listview items.au3 4) First column checkbox and icon.au3 5) Check boxes and icons.au3 6) Background and text colors.au3 7) ListView notifications.au3 8) Header notifications.au3 9) Rearrange columns.au3 A) Multi-line header 1.au3 A) Multi-line header 2.au3 C) Button control.au3 D) _GUICtrlListView_AddItem.au3 E) Large images in first column.au3 F) Storing all lines in array.au3 GuiHeaderEx.au3 GuiListViewEx.au3 ListViewCustomDraw.au3 Images\ 8 images for example E You need AutoIt 3.3.10 or later. Tested on Windows 7 32/64 bit and Windows XP 32 bit. Comments are welcome. Let me know if there are any issues. (Set tab width = 2 in SciTE to line up comments by column.) ListviewMultilineItems.7z2 points -
About the indent problems: Can you test the update at post #574? About cursor color: All the settings will be aviable with the next update in the program settings. Ant the tab key for intelisense: I will check it1 point
-
I think http://www.regexr.com do not support the (?i) but you need to add the flag /i in the Flags menu Did you try the AutoIt code I posted ? You can also use this website : https://regex101.com/r/wY9aR2/11 point
-
I have also used a hidden GUI control in the past to contain a message that can change in GUI 1 and be read with AdLibRegister on a regular interval by GUI 2. Another method is TCP / UDP.1 point
-
1 point
-
I know what you mean TD, but you can compare them, you should think about whether you really believe it when you hear things like "apples and oranges, you cannot compare". Of course you can. You compare completely different things probably 100 times per second in your head. here's some free advice friend, and it applies to all aspects of everything. You know when you take something for granted, and/or as a given and accepted rule of thumb? Don't take it for granted and don't just accept a rule of thumb.1 point
-
Dude, And where do we find this function so we can take a look? M231 point
-
Glad to see it works for you too. There must have been something gone mad with the previous AutoIt environment/setup causing havoc. Reading the document mentionned above, you'll see that you can use pragma hexkey = 'A40F38D5CC20E6' to use a hex password and pragma hexrekey to change it to some other hex value. If you look at a non-encrypted DB you'll see the string "SQLite format 3" at the start of file and the DB DML in plain text following the header, but once the DB is encrypted, nothing is human- readable, not even a little hint that this file is an SQLite DB. @guinness, Yes, the static mixed-mode assembly carries the standard C DLL interface plus the .net one (hence the mixed-mode term). This worked from day one of this library and still does now that Joe Mistachkin has taken the over .Net development as part of the official SQLite dev team. It's now unlikely that encryption support will be dropped for there are way too many users relying on it.1 point
-
Not Ideal, but better than nothing. Actually, try... ControlSend("Plot of " & $svar,"",3,"{Space}") Instead of Send.1 point
-
I just changed the following lines and it seems to work again with new AutoiT version. lines changed: 32, 33, 35, 390, 391, 429 In short. I just Changed the lines containing the _ArrayCreate() function now no more available with the direct array creation sintax, allowed by new versions of AutoIt Here the changed line Global $barrier = [-1] ; ex line 32 Global $barrier = _ArrayCreate(-1) Global $data[16][16] ; ex line 33 Dim $data[16][16] Global $gridboxes = ["none"] ; ex line 35 Dim $gridboxes = _ArrayCreate("none") Local $openlist = ["empty"] ; ex line 390 Local $openlist = _ArrayCreate("empty") ;start with empty open list Local $closedlist = ["empty"] ; ex line 391 Local $closedlist = _ArrayCreate("empty") ;start with empty closed list Local $path = [$ending_node[0]] ; ex line 429 Local $path = _ArrayCreate($ending_node[0]) ;start from goal node Here the whole script modified as indicated in the above lines ;============================== ; Author: Toady ; Site: www.itoady.com ; Updated: May 21, 2008 ; AutoIt Ver: 3.2.12.0 ; ; A * Searching Alorithm ; Artificial Intelligence ; Robot path finding ;============================== #include <array.au3> #include <StaticConstants.au3> #include <GuiConstants.au3> #include <WindowsConstants.au3> ;==================== START OF MAIN ================= ; Example GUI by, Hallman and Toady ProcessSetPriority("Autoit3.exe", 4) Global $first_label = 0 Global $last_label = 0 Global Const $rows = 16 Global Const $cols = 16 Global $estimate Global $closedList_data Global $closedList_Str = "_" Global $openList_Str = "_" Global $start_handel[3] Global $end_handel[3] Global $barrier = [-1] ; Global $barrier = _ArrayCreate(-1) Global $data[16][16] ; Dim $data[16][16] $MainWindow = GUICreate("A * Search Algorithm - Bot pathing", 400, 540) Global $gridboxes = ["none"] ; Dim $gridboxes = _ArrayCreate("none") For $i = 1 To 16 Step 1 For $ii = 1 To 16 Step 1 If $i <> 1 And $i <> 16 And $ii <> 1 And $ii <> 16 Then $temp = GUICtrlCreateLabel("", (($i - 1) * 25), (($ii - 1) * 25), 25, 25, $SS_SUNKEN ) _ArrayAdd($gridboxes, $temp) GUICtrlSetBkColor(-1, 0x000000) Else $temp = GUICtrlCreateLabel("", (($i - 1) * 25), (($ii - 1) * 25), 25, 25) GUICtrlSetBkColor(-1, 0x0220099) EndIf $data[$i - 1][$ii - 1] = "x" If $i = 1 And $ii = 1 Then $first_label = $temp $start_handel[0] = $temp $start_handel[1] = 1 $start_handel[2] = 1 EndIf If $i = 16 And $ii = 16 Then $last_label = $temp $end_handel[0] = $temp $end_handel[1] = 16 $end_handel[2] = 16 EndIf If $ii = 1 Or $ii = 16 Then _ArrayAdd($barrier, $temp) EndIf Next Next Dim $map = $data Dim $resetData = $data $Wall_Radio = GUICtrlCreateRadio("Wall", 90, 410, 50, 20) GUICtrlSetState($Wall_Radio, $GUI_CHECKED) $Space_Radio = GUICtrlCreateRadio("Flat ground", 10, 410, 80, 20) $sand_Radio = GUICtrlCreateRadio("Sand", 10, 450, 70, 20) $water_Radio = GUICtrlCreateRadio("Water", 10, 475, 70, 20) $hill_Radio = GUICtrlCreateRadio("Hill", 10, 500, 70, 20) $Start_Radio = GUICtrlCreateRadio("Start", 150, 410, 70, 20) $End_Radio = GUICtrlCreateRadio("Goal", 220, 410, 70, 20) GUICtrlSetBkColor($End_Radio, 0xff0000) GUICtrlCreateGroup("Searching Heuristic", 80, 435, 200, 100) $md_Radio = GUICtrlCreateRadio("Manhattan", 100, 450, 70, 20) GUICtrlSetState(-1, $GUI_CHECKED) $ed_Radio = GUICtrlCreateRadio("Euclidean", 180, 450, 70, 20) GUICtrlCreateGroup("", -99, -99, 1, 1) ;close group GUICtrlSetBkColor($Start_Radio, 0x00ff00) GUICtrlSetBkColor($End_Radio, 0xff0000) $show_searched_nodes = GUICtrlCreateCheckbox("Show searched nodes (SN)", 100, 470) $allow_diagonals = GUICtrlCreateCheckbox("Allow diagonal moves", 100, 490) $allow_overestimate = GUICtrlCreateCheckbox("Overestimate", 100, 510) GUICtrlSetTip($allow_overestimate, "Faster, no guaranteed shortest path") $go_btn = GUICtrlCreateButton("Go!", 300, 410, 80, 20) $reset_btn = GUICtrlCreateButton("Reset", 300, 435, 80, 20) GUICtrlCreateLabel("Total cost:", 300, 470) $total_cost = GUICtrlCreateLabel("0", 355, 470, 40) GUICtrlCreateLabel("Nodes:", 300, 490) $total_nodes = GUICtrlCreateLabel("0", 355, 490, 40) GUICtrlCreateLabel("Time (ms):", 300, 510) $total_time = GUICtrlCreateLabel("0", 355, 510, 40) Global $Sel_Type = 1 GUISetState() While 1 $msg = GUIGetMsg() Select Case $msg = $GUI_EVENT_CLOSE Exit Case $msg = $go_btn $buffer = "" $closedList_Str = "_" $openList_Str = "_" Global $heuristic = BitAND(GUICtrlRead($md_Radio), $GUI_CHECKED) $SLocation = _GetStartingLocation($data, $rows, $cols) ;starting location $GLocation = _GetGoalLocation($data, $rows, $cols) ;goal location If $SLocation = 0 Or $GLocation = 0 Then MsgBox(0, "Error", "A Goal and a Start must be placed") $map = $data Else Dim $temp[16][16] $temp = $data Local $allow_overestimate_Boolean = BitAND(GUICtrlRead($allow_overestimate), $GUI_CHECKED) If $allow_overestimate_Boolean = 1 Then $estimate = 1.001 ;used to overestimate heuristic by a small amount Else $estimate = 1 EndIf Global $allow_diagonals_Boolean = BitAND(GUICtrlRead($allow_diagonals), $GUI_CHECKED) SplashTextOn("A * Algorithm processing", "Please wait until bot is finished", 200, 100) GUICtrlSetState($go_btn, $GUI_DISABLE) GUICtrlSetState($reset_btn, $GUI_DISABLE) $map = $data _CreateMap($data, $cols, $rows) ;replaces data with node objects Local $timer = TimerInit() Dim $path = _FindPath($data, $data[$SLocation[1]][$SLocation[0]], $data[$GLocation[1]][$GLocation[0]]) Local $timerend = TimerDiff($timer) $closedList_data = StringSplit($closedList_Str, '_', 1) ;not part of algorithm, used in gui GUICtrlSetData($total_nodes, UBound($closedList_data) - 4) ;used in gui also GUICtrlSetData($total_time, Round($timerend, 0)) SplashOff() ;display searched nodes Local $show_searched_Boolean = BitAND(GUICtrlRead($show_searched_nodes), $GUI_CHECKED) If $show_searched_Boolean Then Dim $searchedNodes[UBound($closedList_data) ] For $i = 3 To UBound($closedList_data) - 3 Local $coord = StringSplit($closedList_data[$i], ",") Local $coord_last = StringSplit($closedList_data[$i - 1], ",") $searchedNodes[$i] = GUICtrlCreateLabel(" SN", $coord[1] * 25, $coord[2] * 25, 25, 25, BitOR($SS_SUNKEN, $SS_CENTERIMAGE)) ;GUICtrlSetBkColor(-1, $GUI_BKCOLOR_TRANSPARENT) ;uses this if you want transparent nodes GUICtrlSetBkColor(-1, 0xFFFF00) Sleep(100) Next EndIf If IsArray($path) Then ;if path exists Dim $trail[UBound($path) ] $label = GUICtrlCreateLabel("", 0, 0, 25, 25) Local $last_temp[3] = [2, 1, 1] For $i = 0 To (UBound($path) - 1) Step 1 If UBound($path) = 1 Then Local $nextToCoord = StringSplit($path[0], ",") GUICtrlSetPos($label, $nextToCoord[1] * 25, $nextToCoord[2] * 25) GUICtrlSetBkColor(-1, 0xccffff) GUICtrlSetData($total_cost, 1) ExitLoop EndIf $temp = $path[$i] $temp = StringSplit(StringReplace($temp, "|", ""), ",") If $i > 0 Then $last_temp = $path[$i - 1] $last_temp = StringSplit(StringReplace($last_temp, "|", ""), ",") EndIf GUICtrlSetPos($label, $temp[2] * 25, $temp[1] * 25) GUICtrlSetBkColor(-1, 0xccffff) $trail[$i] = GUICtrlCreateLabel("", $temp[2] * 25, $temp[1] * 25, 25, 25, $SS_SUNKEN) GUICtrlSetBkColor($trail[$i], 0x00dddd) Local $obj = $data[$temp[2]][$temp[1]] GUICtrlSetData($total_cost, Round($obj[3], 2)) If Abs($temp[1] - $last_temp[1]) + Abs($temp[2] - $last_temp[2]) < 2 Then Sleep(200) Else Sleep(280) ;delay a little long to make animation smooth EndIf Next Sleep(500) GUICtrlDelete($label) For $i = 0 To UBound($path) - 1 GUICtrlDelete($trail[$i]) Next If $show_searched_Boolean Then If IsArray($searchedNodes) Then For $i = 0 To UBound($searchedNodes) - 1 GUICtrlDelete($searchedNodes[$i]) Next EndIf EndIf GUICtrlSetState($Wall_Radio, $GUI_CHECKED) $data = $map Else MsgBox(0, "", "No path to goal") ;if goal can't be reached If $show_searched_Boolean Then If IsArray($searchedNodes) Then For $i = 0 To UBound($searchedNodes) - 1 GUICtrlDelete($searchedNodes[$i]) Next EndIf EndIf $data = $map GUICtrlSetState($Wall_Radio, $GUI_CHECKED) $Sel_Type = 1 EndIf GUICtrlSetState($go_btn, $GUI_ENABLE) GUICtrlSetState($reset_btn, $GUI_ENABLE) EndIf Case $msg >= $first_label + 16 And $msg <= $last_label - 16 And _ArraySearch($barrier, $msg) < 0 $sel_X = Ceiling(($msg - $first_label + 1) / 16) $sel_Y = Ceiling($msg - $first_label + 1) - ($sel_X - 1) * 16 If $Sel_Type = 1 Then GUICtrlSetBkColor($msg, 0x000000) $data[$sel_X - 1][$sel_Y - 1] = "x" ElseIf $Sel_Type = 2 Then GUICtrlSetBkColor($msg, 0xeeeeee) $data[$sel_X - 1][$sel_Y - 1] = "1" ElseIf $Sel_Type = 3 Then If $data[$start_handel[1] - 1][$start_handel[2] - 1] = "s" Then GUICtrlSetBkColor($start_handel[0], 0xeeeeee) $data[$start_handel[1] - 1][$start_handel[2] - 1] = "1" EndIf $start_handel[0] = $msg $start_handel[1] = $sel_X $start_handel[2] = $sel_Y GUICtrlSetBkColor($msg, 0x00ff00) $data[$sel_X - 1][$sel_Y - 1] = "s" ElseIf $Sel_Type = 4 Then If $data[$end_handel[1] - 1][$end_handel[2] - 1] = "g" Then GUICtrlSetBkColor($end_handel[0], 0xeeeeee) $data[$end_handel[1] - 1][$end_handel[2] - 1] = "1" EndIf $end_handel[0] = $msg $end_handel[1] = $sel_X $end_handel[2] = $sel_Y GUICtrlSetBkColor($msg, 0xff0000) $data[$sel_X - 1][$sel_Y - 1] = "g" ElseIf $Sel_Type = 5 Then;sand GUICtrlSetBkColor($msg, 0xFFdd44) $data[$sel_X - 1][$sel_Y - 1] = "2" ;2 times harder than flat ground ElseIf $Sel_Type = 6 Then;water GUICtrlSetBkColor($msg, 0x2222FF) $data[$sel_X - 1][$sel_Y - 1] = "3" ;3 times harder than flat ground ElseIf $Sel_Type = 7 Then;hill GUICtrlSetBkColor($msg, 0x885500) $data[$sel_X - 1][$sel_Y - 1] = "4" ;4 times harder than flat ground EndIf Case $msg = $Wall_Radio $Sel_Type = 1 Case $msg = $Space_Radio $Sel_Type = 2 Case $msg = $Start_Radio $Sel_Type = 3 Case $msg = $End_Radio $Sel_Type = 4 Case $msg = $sand_Radio $Sel_Type = 5 Case $msg = $water_Radio $Sel_Type = 6 Case $msg = $hill_Radio $Sel_Type = 7 Case $msg = $reset_btn $data = $resetData For $i = 1 To UBound($gridboxes) - 1 GUICtrlSetBkColor($gridboxes[$i], 0x000000) Next GUICtrlSetData($total_cost, "0") GUICtrlSetData($total_nodes, "0") GUICtrlSetData($total_time, "0") EndSelect WEnd ;==================== END OF MAIN ================= ;============ * How to use this code * ============ ; Below is the A * Searching algorithm and its ; required functions to work. ; This is coded to work with 2D spaces only. ; Everything below is all you need to get started. ; 1. Initialize a 2D array ; $data[5][5] = [["x","x","x","x","x"], _ ; ["x","s","0","x","x"], _ ; ["x","x","0","x","x"], _ ; ["x","x","0","g","x"], _ ; ["x","x","x","x","x"]] ; NOTE: Array MUST have x's around entire paremeter ; There must be a "s" and a "g". "0" means bot can walk here ; 2. Convert array into node objects ; _CreateMap($data,5,5) ; 3. Calculate path ; Dim $path = _FindPath($data,$data[1][1],$data[3][3]) ; 4. Thats all! ; The variable $path contains an array of the path in "x,y" format ; _ArrayDisplay($path) will show the the full path ;================================================== ;============================================================================= ; Replaces data grid with node objects ; Converts $data into a 2D array of node objects from previous $data array ; consisting of only string characters. ;============================================================================= Func _CreateMap(ByRef $data, $x, $y) ;converts a 2D array of data to node objects For $i = 0 To $y - 1 ;for each row For $j = 0 To $x - 1 ;for each column If StringRegExp($data[$i][$j], "[x,s,g]") <> 1 Then;if not a x,s,g $data[$i][$j] = _CreateNode($i & "," & $j, "null", 0, $data[$i][$j], 0, $data[$i][$j]) Else If $data[$i][$j] = "s" Then $data[$i][$j] = _CreateNode($i & "," & $j, "null", 0, 0, 0, $data[$i][$j]) Else $data[$i][$j] = _CreateNode($i & "," & $j, "null", 0, 1, 0, $data[$i][$j]) EndIf EndIf Next Next EndFunc ;==>_CreateMap ;============================================================================= ; Creates a node struct object with the following parameters ; struct node { ; char self_coord[8]; // Format = "x,y" ; char parent_coord[8]; // Format = "x,y" ; int f; // F = G + H ; int g; // G = current cost to this node from start node ; int h; // H = Heuristic cost, this node to goal node ; char value[8]; // Type of node (ex. "s","g","x","1,2,3..n") ; int cost; // Cost of node (difficulty of traveling on this) ; } ;============================================================================= Func _CreateNode($self, $parent, $f, $g, $h, $value) ;returns struct object Local $node[6] = [$self, $parent, $f, $g, $h, $value] Return $node EndFunc ;==>_CreateNode ;============================================================================= ; Checks to see if start node exists in map ; Returns an array: [y,x] ;============================================================================= Func _GetStartingLocation(ByRef $data, $cols, $rows) For $i = 0 To $cols - 1 For $j = 0 To $rows - 1 If $data[$i][$j] = "s" Then Local $pos[2] = [$j, $i] Return $pos EndIf Next Next Return 0 ;no starting location found EndFunc ;==>_GetStartingLocation ;============================================================================= ; Checks to see if goal node exists in map ; Returns an array: [y,x] ;============================================================================= Func _GetGoalLocation(ByRef $data, $cols, $rows) For $i = 0 To $cols - 1 For $j = 0 To $rows - 1 If $data[$i][$j] = "g" Then Local $pos[2] = [$j, $i] Return $pos EndIf Next Next Return 0 ;no starting location found EndFunc ;==>_GetGoalLocation ;============================================================================= ; Calculates the manhattan distance between two nodes ; MD = |G(x) - N(x)| + |G(y) - N(x)| ; Returns an integer ;============================================================================= Func _MD(ByRef $node, ByRef $goal) ;returns integer Local $node_coord = StringSplit($node[0], ",") ;current node Local $goal_coord = StringSplit($goal[0], ",") ;goal node Return (Abs($goal_coord[1] - $node_coord[1]) + Abs($goal_coord[2] - $node_coord[2])) * $estimate EndFunc ;==>_MD ;============================================================================= ; Calculates the Euclidean distance between two nodes ; MD = SquareRoot ( (G(x) - N(x))^2 + (G(y) - N(x))^2 ) ; Returns an integer ;============================================================================= Func _ED(ByRef $node, ByRef $goal) ;returns integer Local $node_coord = StringSplit($node[0], ",") ;current node Local $goal_coord = StringSplit($goal[0], ",") ;goal node Return Sqrt(($goal_coord[1] - $node_coord[1]) ^ 2 + ($goal_coord[2] - $node_coord[2]) ^ 2) * $estimate EndFunc ;==>_ED ;============================================================================= ; A * Searching Algorithm ; Keep searching nodes until the goal is found. ; Returns: Array if path found ; Returns: 0 if no path ;============================================================================= Func _FindPath(ByRef $map, $start_node, $goal_node) ;returns array of coords Local $openlist = ["empty"] ; Local $openlist = _ArrayCreate("empty") ;start with empty open list Local $closedlist = ["empty"] ; Local $closedlist = _ArrayCreate("empty") ;start with empty closed list Local $current_node = $start_node ;set current node to start nodeF $closedList_Str &= $current_node[0] & "_" $openList_Str &= $current_node[0] & "_" _AddAdjacents_Openlist($map, $openlist, $closedlist, $current_node, $goal_node) ;add all possible adjacents to openlist While 1 ;while goal is not in closed list, or open list is not empty If UBound($openlist) = 1 Then ExitLoop ;if open list is empty then no path found $current_node = _GetLowest_F_Cost_Node($openlist) ;pick node with lowest F cost $closedList_Str &= $current_node[0] & "_" _AddAdjacents_Openlist($map, $openlist, $closedlist, $current_node, $goal_node) ;add all possible adjacents to openlist If $current_node[0] = $goal_node[0] Then ExitLoop ;if current node is goal then path is found! WEnd If _IsInClosedList($goal_node[0]) = 0 Then ;if no goal found then return 0 Return 0 ; no path found Else Return _GetPath($map, $current_node, $start_node) ;return array of coords (x,y) in string format EndIf EndFunc ;==>_FindPath ;============================================================================= ; Returns node object with the lowest F cost ; F = G + H ; Returns 0 with openlist is emtpy, there is no path ;============================================================================= Func _GetLowest_F_Cost_Node(ByRef $openlist) If UBound($openlist) > 1 Then ;If open list is not empty Local $obj = $openlist[1] ;Pop first item in the queue _ArrayDelete($openlist, 1) ;remove this node from openlist Return $obj ;return lowest F cost node EndIf Return 0 ;openlist is empty EndFunc ;==>_GetLowest_F_Cost_Node ;============================================================================= ; Start from goal node and traverse each parent node until starting node is ; reached. ; Each node will have a parent node (use this to get path bot will take) ; Returns: Array of coords, first index is starting location ;============================================================================= Func _GetPath(ByRef $data, ByRef $ending_node, ByRef $start_node) Local $path = [$ending_node[0]] ; Local $path = _ArrayCreate($ending_node[0]) ;start from goal node Local $node_coord = StringSplit($path[0], ",") Local $x = $node_coord[1] Local $y = $node_coord[2] Local $start = $start_node[0] ;starting nodes coord Local $obj = $data[$x][$y] ;current node starting from the goal While $obj[1] <> $start ;keep adding until reached starting node _Add_List($path, $y & "," & $x) ;add the parent node to the list $obj = $data[$x][$y] ;get node from 2D data array $node_coord = StringSplit($obj[1], ",") If $node_coord[0] = 1 Then ExitLoop $x = $node_coord[1] $y = $node_coord[2] WEnd _ArrayDelete($path, 0) ;no need to starting node _ArrayReverse($path) ;flip array to make starting node at index 0 Return $path ;return path as array in "x,y" format for each item EndFunc ;==>_GetPath ;============================================================================= ; Adds adjacent nodes to the open list if: ; 1. Node is not a barrier "x" ; 2. Node is not in open list ; 3. Node is not in closed list ; Set newly added node's parent to the current node and update its F,G, and H ; Only need to check North, South, East and West nodes. ;============================================================================= Func _AddAdjacents_Openlist(ByRef $data, ByRef $openlist, ByRef $closedlist, ByRef $node, ByRef $goal) Local $current_coord = StringSplit($node[0], ",") Local $x = $current_coord[1] Local $y = $current_coord[2] Local $h ; heuristic Local $north = 0 Local $south = 0 Local $east = 0 Local $west = 0 Local $obj = $data[$x][$y - 1] If $obj[5] <> "x" And _ ;north Not _IsInAnyList($obj[0]) Then ;If not in closed list or openlist and is not a barrier If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + $obj[3] ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x][$y - 1] = $obj $north = 1 $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf $obj = $data[$x][$y + 1] If $obj[5] <> "x" And _ ;south Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + $obj[3] ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x][$y + 1] = $obj $south = 1 $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf $obj = $data[$x + 1][$y] If $obj[5] <> "x" And _ ;east Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + $obj[3] ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x + 1][$y] = $obj $east = 1 $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf $obj = $data[$x - 1][$y] If $obj[5] <> "x" And _ ;west Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + $obj[3] ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x - 1][$y] = $obj $west = 1 $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf ;diagonals moves If $allow_diagonals_Boolean Then ;if GUI checkbox is checked, then check other 4 directions If $north + $east = 2 Then ;Not allowed to cut around corners, not realistic $obj = $data[$x + 1][$y - 1] If $obj[5] <> "x" And _ ;northeast Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + (Sqrt(2) * $obj[3]) ;set g score (current node's G score + adjacent node's G score* Sqrt(2)) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x + 1][$y - 1] = $obj $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf EndIf If $north + $west = 2 Then $obj = $data[$x - 1][$y - 1] If $obj[5] <> "x" And _ ;north west Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + (Sqrt(2) * $obj[3]) ;set g score (current node's G score + adjacent node's G score* Sqrt(2)) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x - 1][$y - 1] = $obj $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf EndIf If $south + $east = 2 Then $obj = $data[$x + 1][$y + 1] If $obj[5] <> "x" And _ ;southeast Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + (Sqrt(2) * $obj[3]) ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x + 1][$y + 1] = $obj $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf EndIf If $south + $west = 2 Then $obj = $data[$x - 1][$y + 1] If $obj[5] <> "x" And _ ;southwest Not _IsInAnyList($obj[0]) Then If $heuristic = 1 Then $h = _MD($obj, $goal) Else $h = _ED($obj, $goal) EndIf $obj[1] = $node[0] ;set nodes parent to last node $obj[3] = $node[3] + (Sqrt(2) * $obj[3]) ;set g score (current node's G score + adjacent node's G score) $obj[2] = $obj[3] + $h ;set f = g + h score $data[$x - 1][$y + 1] = $obj $openList_Str &= $obj[0] & "_" _Insert_PQ($openlist, $obj) EndIf EndIf EndIf EndFunc ;==>_AddAdjacents_Openlist ;============================================================================= ; Returns true if node is in closed list ; Search the list backwards, its faster ;============================================================================= Func _IsInClosedList(ByRef $node) If StringRegExp($closedList_Str, "_" & $node & "_") Then Return 1 Else Return 0 EndIf EndFunc ;==>_IsInClosedList ;============================================================================= ; Returns true if node is in open list ; Regular expressions are used rather than searching an array list for speed. ;============================================================================= Func _IsInAnyList(ByRef $node) If StringRegExp($openList_Str, "_" & $node & "_") Then Return 1 Else Return 0 EndIf EndFunc ;==>_IsInAnyList ;============================================================================= ; Inserts object into openlist and preserves ascending order ; This way will result in a priority queue with the lowest F cost at ; position 1 in the openlist array. ;============================================================================= Func _Insert_PQ(ByRef $openlist, $node) Local $obj For $i = 1 To UBound($openlist) - 1 Local $obj = $openlist[$i] If $node[2] < $obj[2] Then _ArrayInsert($openlist, $i, $node) Return EndIf Next _Add_List($openlist, $node) EndFunc ;==>_Insert_PQ ;============================================================================= ; Adds nodes the a list ;============================================================================= Func _Add_List(ByRef $list, $node) ReDim $list[UBound($list) + 1] $list[UBound($list) - 1] = $node EndFunc ;==>_Add_List ;============================================================================ ; End of Algorithm ;============================================================================1 point