PDA

View Full Version : Control_GetText and Control_SetText



rjp74
28-08-2012, 14:08
Hi,
I am trying to use ThinBasic to automate an existing win32 graphics application (xara).
However, I seem unable to make it work. The Control_GetText and Control_SetText calls appear to have no effect.

I can get the application handle easily using this code.
hWnd = Win_FindByTitle("Xara...")
I know the controlID using WinSpy
controlID = 14377

However, in ThinBasic I have not found a way to make the following have any effect:
EditText= CONTROL_GetText(hwnd, controlID)
s = CONTROL_SetText(hwnd, controlID, "10cm")
There are no errors and nothing appears to happen.

If I do this for a dialogue I create in Thinbasic it all seems to work ok. But for other apps nothing.
I can make AutoIT and Autokey work perfectly but prefer to use ThinBasic due to its langauge structure and debugging capabilities.

Can someone tell me the correct way to get and set text in the other applications edit boxes please.

ErosOlmi
28-08-2012, 14:50
Ciao rjp74 and welcome to thinBasic community.

CONTROL_GetText and CONTROL_SetText work using the window handle of the parent window containing the control plus the control id
Win_FindByTitle instead just return the windows handle of the window whose title is ... something your search for.

So you need the windows handle of the parent control (that is a dynamic internal handle that change at every window creation) plus the control ID (that is always the same)

Finding the parent window is not that easy in some situations because complex user interfaces there can have hidden windows of windows not easily identifiable.

Can you please post a picture of the are you area trying to get?

rjp74
28-08-2012, 16:24
[QUOTE=Can you please post a picture of the are you area trying to get?[/QUOTE]
Thank you for your comments. Here is a picture showing the main parts I am in interested in automating. The arrows show the main controls I am trying to access.
The text boxes are only meaningful when the green highlighted panel is active. I have to add code that ensures this, or do it by hand for testing purposes.

7923

If I was doing this in AutoIT I would write the following code. This works perfectly.



Const $zara = "[CLASS:XTPMainFrame]"
Const $xX = 14377
; -- set "X" edit box to contain the text "1.00cm"
ControlSetText($zara, "1.00cm", $xX)


Setting the text is one thing - I also need to send an {Enter} to get xara to action the new value. This I can do through a second call. I only need do it after all the values have been entered.
First I need to get some form of set text and get text working. I seem to be totallly stuck with this.

Perhaps you have an example showing how to set or read the display text box for something simple such as the standard Windows Calculator? Given a working example I am sure I could discover how to make the same thing work for my xara script.
many thanks

Petr Schreiber
28-08-2012, 21:34
Hi rjp74,

maybe you could try a bit more low level approach:


' -- Add these functions to your code
Function Custom_SetText( hWindow As DWord, idCtl As Long, sText As String )

Dim szText As Asciiz * 1024
szText = sText

DWord hCtl = Win_GetDlgItem(hWindow, idCtl)

SendMessage hCtl, %WM_SETTEXT, 0, VarPtr(szText)

End Function

Function Custom_SendEnterKey( hWindow As DWord, idCtl As Long )

DWord hCtl = Win_GetDlgItem(hWindow, idCtl)

Win_SetFocus( hCtl )
SendKeys("{ENTER}")

End Function


You can use this code like:


DWord hWnd = Win_FindByTitle("Xara...")
Long controlID = 14377
Custom_SetText(hWnd, controlID, "1.00cm")
Custom_SendEnterKey(hWnd, controlID)



Petr

ErosOlmi
28-08-2012, 23:34
Dear rjp74,

I have bad news, sorry :cry:

The main problem is that thinBasic UI module is not designed to automate controls in process different from current thinBasic process. AutoIt instead was initially designed to control windows in whatever process they are. For this reason AutoIt developer designed some specific commands (in this case ControlSetText) to be able to discover and manage child controls.

In details, CONTROL SET TEXT uses Windows API called SetWindowText (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633546(v=vs.85).aspx) and this API is able to set text only in the controls created by the current process (that is in windows created by thinBasic scripts). To SET TEXT into controls not belonging into current process, Microsoft suggest to use SendMessage (http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx) to send a %WM_SETTEXT (http://msdn.microsoft.com/en-us/library/windows/desktop/ms632644(v=vs.85).aspx) message to the window you want to change text. The disadvantage of this process is that you need to know the exact window handle of the control you want to change the text content and this is dynamic.

So, for the moment I have no solution for your request.
I will study AutoIt ControlSetText function to see if I can add something similar to thinBasic.

Ciao
Eros

rjp74
29-08-2012, 00:42
Hi,

Thanks for informing me that for the moment there is no solution. I am relieved that it was not caused by my making some basic mistakes.

I tried the code suggested by Petr Schreiber but it failed on this line of code:
DWord hCtl = Win_GetDlgItem(hWindow, idCtl)
It returns 0 instead of the expected value. Here is the trace and the Au3Info display for the specified control.

7924

I did look up the web page for the source for AutoIT. It is here:
https://github.com/AutoHotkey/AutoHotkey/blob/master/Source/script2.cpp
I am not a c++ or a windows deep internals programmer but it seems that the magic occurs in a routine called DetermineTargetWindow()

many thanks for your help

ErosOlmi
29-08-2012, 00:49
Thanks a lot for the AutoIt link.
As I suspected, it is using SendMessage with %WM_SETTEXT message



ResultType Line::ControlSetText(char *aControl, char *aNewText, char *aTitle, char *aText
, char *aExcludeTitle, char *aExcludeText)
{
g_ErrorLevel->Assign(ERRORLEVEL_ERROR); // Set default ErrorLevel.
HWND target_window = DetermineTargetWindow(aTitle, aText, aExcludeTitle, aExcludeText);
if (!target_window)
return OK;
HWND control_window = ControlExist(target_window, aControl); // This can return target_window itself for cases such as ahk_id %ControlHWND%.
if (!control_window)
return OK;
// SendMessage must be used, not PostMessage(), at least for some (probably most) apps.
// Also: No need to call IsWindowHung() because SendMessageTimeout() should return
// immediately if the OS already "knows" the window is hung:
DWORD result;
SendMessageTimeout(control_window, WM_SETTEXT, (WPARAM)0, (LPARAM)aNewText
, SMTO_ABORTIFHUNG, 5000, &result);
DoControlDelay;
return g_ErrorLevel->Assign(ERRORLEVEL_NONE); // Indicate success.
}

I think the trick is inside ControlExist function. It should be able to enumerate though all child controls even if in between the main windows and the needed control there are hidden windows like in the case of XARA where the Edit control you want to change is inside other two parent windows.

I will see what I can do. It is quite interesting having something similar.

Ciao
Eros

ErosOlmi
29-08-2012, 01:12
I think I've found a piece of Power Basic code I can use as a starter.
http://www.powerbasic.com/support/pbforums/showthread.php?t=23634&highlight=EnumChildWindows

The API to be used is EnumChildWindows
Once I've the handle of the main Window (in this case Xara) I will use EnumChildWindows to enumerate all child windows (controls) of a certains class or having a certain Control ID.
Once found I can use SendMessage ... %WM_SETTEXT to fill the requested control with required text

Should be quite easy but I need some days to develop and test.

I will let your know.
Eros

ErosOlmi
29-08-2012, 07:41
I've downloaded and installed Xara Designer 7 in order to test it.
Than created a PB application to see if the idea I had is working or not and it seems all is working fine like in AutoIt.

So ...I think by this evening I will replicate AutoIt function called ControlSetText
Initially I will create a specific module to be tested in thinBasic scripts. If tests will be OK, I will include into UI module

Stay tuned ...

rjp74
29-08-2012, 12:08
Hi, I made some progress. I needed to write a recursive control finder that returned the parent handle for that control. Then I could correctly use the send message function.
The code doesnt do much - it simply writes "1.00cm" into the X text box of Xara.
Next task is to find out how to simulate the pressing of the enter key to get the value accepted.....
After that I will need mouse clicks, and control clicks. I can live without control_getText for now but I can see that other people would find this very helpful.


' Empty ThinBASIC CONSOLE file template
Uses "UI"
Uses "console"
Declare Function GetWindow Lib "user32.dll" Alias "GetWindow" (ByVal hWnd As DWord, ByVal uCmd As Long) As Long
Declare Function GetDlgCtrlID Lib "user32.dll" Alias "GetDlgCtrlID" (ByVal hwndCtl As DWord) As Integer


DWord hWnd = Win_FindByTitle("Xara...")
Long cId_X = 14377
DWord Parent = 0

Parent = FindHandle(hWnd, cId_X)
Console_WriteLine Str$(Parent)
Custom_SetText(Parent, cId_X, "1.00cm")
Console_WaitKey

Function FindHandle(hWnd As DWord, myCtrlID As Long ) As DWord
Long hChild = GetWindow(hWnd, %GW_CHILD)
While hChild
Long thisCtrlId = GetDlgCtrlID(hChild)
If thisCtrlId = myCtrlID Then Return hChild ' found it
Long FoundParent = FindHandle(hChild, myCtrlID)
If (FoundParent > 0) Then Return FoundParent ' no need to search further
hChild = GetWindow(hChild, %GW_HWNDNEXT)
Wend
Return 0 ' not found
End Function

Function Custom_SetText( hWindow As DWord, idCtl As Long, sText As String)
Dim szText As Asciiz * 1024
szText = sText
SendMessage hWindow, %WM_SETTEXT, 0, VarPtr(szText)
End Function

ErosOlmi
29-08-2012, 18:43
Regarding pressing the enter key, have you just tested to append a $CRLF or a single $CR or $LF to your text:

Custom_SetText(Parent, cId_X, "1.00cm" & $CRLF)

On using some API, thinBasic has already wrapped some the UI Windows API. Check in thinBasic offline help in UI module or online at: http://www.thinbasic.com/public/products/thinBasic/help/html/index.html?windows.htm
For example GetDlgCtrlID eis already present as native function in UI module as Win_GetDlgCtrlID

Your "FindHandle" function is very interesting and is more or les the same strategy used by AutoIt ControlSetText function for finding child control having parent and control ID.

This evening I will work on native thinBasic ControlSetText

Thanks for using thinBasic.
Eros

rjp74
29-08-2012, 20:14
Hi Eros,
Yes I have tried with $CRLF - it does not have the same effect as keypress {Enter}.
$CRLF does not create an event whereas the simulated keypress does. It is this event that is critical to making things work.
I tidied up and shrank the code to find the control handle quite a bit and gave things better names. Here is my latest version:


' given the handle of an application deep search for a control and return its handle
Function FindCtrlHandle(hWnd As DWord, myCtrlID As Long ) As DWord
Long hChild = GetWindow(hWnd, %GW_CHILD)
While hChild
If GetDlgCtrlID(hChild) = myCtrlID Then Return hChild ' found it
Long hCtrl = FindCtrlHandle(hChild, myCtrlID)
If (hCtrl > 0) Then Return hCtrl ' no need to search further
hChild = GetWindow(hChild, %GW_HWNDNEXT)
Wend
Return 0 ' not found
End Function

I also tried to create code for sendkeys


Function xaraPressEnter(ctrlID As Long)
Long hCtrl = FindCtrlHandle(zarahWnd, ctrlID)
Control Set Focus hctrl, ctrlID
SendKeys("{ENTER}")
End Function

I know the handles and controlIDs are correct as I can address the required control correctly. However, using this code the set focus does not work and the {Enter} is applied to ThinAir instead.

I am starting to understand a comment from earlier that eveything that is not owned by ThinAir must be addressed using the low level SendMessage.
In a subsequent post I will send an AutoIT script that does something useful in Xara so that you can see what I am doing with this.

rjp74
29-08-2012, 21:15
HI,
here is the autoIT example (attached zip) for Xara that I was originally hoping to rewrite using Thinbasic.
Start xara and then assuming you have autoIT installed run the attached xara Circle of Circles.au3
A circle will be created. Then using cut and paste be pasted in many times in the form of a larger circle.
There are various parameters one can set - here I make an elipse shape.
7926

ErosOlmi
29-08-2012, 21:53
I've installed Xara and tested the script but unfortunately after few commands it GPF Xara.
In any case I've got the example.

To send an ENTER key to a window, you can use the SendMessage again in this way:

SendMessage hWindow, %WM_KEYDOWN, %VK_RETURN, 0

Something like:


Function Custom_SetText(hWindow As DWord, idCtl As Long, sText As String, Optional SendEnter As Boolean)
SendMessage hWindow, %WM_SETTEXT, 0, StrPtr(sText)
If SendEnter Then
SendMessage hWindow, %WM_KEYDOWN, %VK_RETURN, 0
End If
End Function

Executed as:


Custom_SetText(Parent, cId_X, "1 cm", %TRUE)


SendMessage is a very precise command because it will reach the exact windows handle specified.

SendKeys is always very dangerous because it sends keyboard keys into the keyboard buffer and not to the target window.
If your target window is not the current one, SendKeys can send keyboard keystrokes to random windows.

Here your thinBasic script a little bit remastered:


Uses "UI"


Declare Function GetWindow Lib "user32.dll" Alias "GetWindow" (ByVal hWnd As DWord, ByVal uCmd As Long) As Long


DWord hWnd = Win_FindByTitle("Xara...")
If hWnd = 0 Then
MsgBox 0, "Xara... Window not found"
Stop
End If

Long cId_X = 14377
DWord Parent = 0
Long lCounter

Parent = FindHandle(hWnd, cId_X)


'---ATTENTION: Here it assumes a Xara object is selected
'--- Object will be moved right by 1 cm for each loop
'--- Loop is paused 100 ms
For lCounter = 1 To 10
Custom_SetText(Parent, lCounter & " cm", %TRUE, 100)
Next


'------------------------------------------------------------------
' Search for a control ID inside a main window
'------------------------------------------------------------------
Function FindHandle(hWnd As DWord, myCtrlID As Long ) As DWord
'------------------------------------------------------------------
Long hChild = GetWindow(hWnd, %GW_CHILD)
While hChild
Long thisCtrlId = Win_GetDlgCtrlID(hChild)
If thisCtrlId = myCtrlID Then Return hChild ' found it
Long FoundParent = FindHandle(hChild, myCtrlID)
If (FoundParent > 0) Then Return FoundParent ' no need to search further
hChild = GetWindow(hChild, %GW_HWNDNEXT)
Wend
End Function


'------------------------------------------------------------------
' Send text to a target window
'------------------------------------------------------------------
Function Custom_SetText(hWindow As DWord, sText As String, Optional SendEnter As Boolean, lsleep As Long)
'------------------------------------------------------------------
'---Send text to target window
SendMessage hWindow, %WM_SETTEXT, 0, StrPtr(sText)
'---If requested, Send ENTER key
If SendEnter Then SendMessage hWindow, %WM_KEYDOWN, %VK_RETURN, 0
'---If requested, Wait some ms
If lSleep Then Sleep lSleep
End Function


Code Updated.

rjp74
30-08-2012, 09:33
The cause of the GPF is a mixture of two things:
1 - the time needed by the target application to process the current command before the next command is issued. This can be managed by issuing sleep() after every command.
2 - where the current focus is.

for 1, it is possible to issue longer sleep commands if one knows the complexity of the action. However, this is not a reliable technique and does not take into account the speed of the computer. The script I sent works ok on my computer but causes GPF on yours. Perhaps my computer is faster? Adding sleep time usually solves the problem. Interestingly, Quick Macros solves the problem by looking at CPU % utilisation and only proceeds when this has dropped to a low level.

for 2, I agree that set focus for send(string) is definitely not a good approach. What is needed is send(hKey, string) . This is especially noticeable when using the debugger as it then has the focus. Send() targets the debugger instead of the target application. Which makes debugging really difficult.

I looked at what I need for basic automation of xara. We have some parts already working:
a - get correct handle of a control
b - set control text
c - send message for individual keys such as enter

What is still needed is:
a - send text using the hKey of the target application. Has a similar effect to user pressing keyboard keys. Calling send message() multiple times perhaps?
b - mouse click at (x,y) relative to target application coordinates - probably needs mouse event from user32.dll
c - mouse drag (x0,y0) to (x1,y1)

What would be nice to have. I don't need it but I believe other people will is:
d - get control text - This will allow for testing and more interactive forms of automation.

If one day this can be done in TinyBasic then I can have just the one application rather than the several I am currently using. Only one language to learn!
However, I have the feeling we are getting there quite quickly!

rjp74
30-08-2012, 13:07
I wrote a mouse click routine for the left mouse button which works.


Const WM_MOUSEMOVE As Long = &H200
Const WM_LBUTTONUP As Long = &H202
Const WM_LBUTTONDOWN As Long = &H201

'------------------------------------------------------------------
' Mouse Click on Target
'------------------------------------------------------------------
Function MouseClick(hWnd, x1 As Integer, y1 As Integer)
Long x1y1 = (x1 And &HFFFF&) Or (y1 * &H10000)
SendMessage hWnd, WM_LBUTTONDOWN, 1&, x1y1
SendMessage hWnd, WM_LBUTTONUP, 1&, x1y1
Sleep(20)
End Function

I also tried, but not 100% succesfully to write a mouse drag routine.
It works with the pen tool (F4) but does not work with lines, circles or rectangles.
I wasn't able to discover why not .


'------------------------------------------------------------------
' Mouse drag on Target
'------------------------------------------------------------------
Function MouseDrag(hWnd, x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer )
Long x1y1 = (x1 And &HFFFF&) Or (y1 * &H10000)
Long x2y2 = (x2 And &HFFFF&) Or (y2 * &H10000)

SendMessage hWnd, WM_MOUSEMOVE, 1&, x1y1
SendMessage hWnd, WM_LBUTTONDOWN,1&, x1y1
Sleep(20)
SendMessage hWnd, WM_MOUSEMOVE, 1&, x2y2
SendMessage hWnd, WM_LBUTTONUP,1&, x2y2
Sleep(20)
End Function

rjp74
30-08-2012, 13:10
I discovered that the code for getting the handle of a control does not always work as expected - especially when there are more than one controls with the same controlID. e.g in Xara the new document has controlID of 100, and so does the select tool.
The only way I can resolve this is by getting the controls text and checking that - but getting the text is blocked in ThinAir. So I am stuck.

ErosOlmi
30-08-2012, 15:03
The ControlID is not an absolute unique ID inside the application but is unique inside its parent Window.
This is by Windows design

So you can have something like:

Main window

Child Control ID 1
Child Window 2

Child control ID 1
Child control ID 2

Child Window 3

Child control ID 1
Child control ID 2

And so on ... depending on how complex is the design of the application.
Just check with WinSpy and you will see.
This is tipical of MDI interfaces like Xara and many other applications using MDI interface: the MDI Window is the same for all MDI open documents so control IDs are the same.

In reality every Child Control is a window (created with either CreateWindow or CreateWindowEx API) each of which has a unique window Handle regardless its ControlID
Having the unique window handle is the only way to be sure you are addressing one control/window or the other.

I'm sorry thinBasic has not so many native functionalities for controlling other windows created by process different from thinBasic itself but the origin of thinBasic didn't start from automation but from general purposes programming.
I will try to add some but it will require a little bit of time.

Petr Schreiber
30-08-2012, 15:41
Hi,

to retrieve text from given handle the following snippet could be used:


Function Custom_GetText( hWnd As DWord ) As String

String sText
' -- Retrieve the text length in target GUI element
Long lText = SendMessage(hWnd, %WM_GETTEXTLENGTH, 0, 0) + 1

' -- Allocate string buffer able to contain the text
sText = Repeat$(lText, $NUL)

' -- Retrieve the text
SendMessage hWnd, %WM_GETTEXT, lText, StrPtr(sText)

' -- ... and pass it out of the function
Return sText

End Function



Petr

rjp74
31-08-2012, 13:42
Thanks Petr,
Your code is just what I needed. Much appreciated.

Here is the full code needed to get the correct handle of a control. Here I am looking for controlID =100 and text ="SELRTOOLICON" in xara.


Uses "UI"
'Uses "Console"

Declare Function GetWindow Lib "user32.dll" Alias "GetWindow" (ByVal hWnd As DWord, ByVal uCmd As Long) As Long
Declare Function GetDlgCtrlID Lib "user32.dll" Alias "GetDlgCtrlID" (ByVal hwndCtl As DWord) As Integer

DWord hWnd = Win_FindByTitle("Xara...")
If hWnd = 0 Then
MsgBox 0, "Xara... Window not found"
Stop
End If

DWord hSelBtn = FindCtrlHandle(hWnd, 100, "SELRTOOLICON", 0)
MsgBox(0, Str$( hSelBtn))
Stop

'------------------------------------------------------------------
' Search for a control ID inside a main window
'------------------------------------------------------------------
Function FindCtrlHandle(hWnd As DWord, myCtrlID As Long, myCtrlText As String, depth ) As DWord
'------------------------------------------------------------------
Long hChild = GetWindow(hWnd, %GW_CHILD)
While hChild
String ctrlText = Custom_GetText(hChild)
Long ctrlID = GetDlgCtrlID(hChild)
' Console_WriteLine String$(depth * 3, " ") & " " & Str$(hChild) & " ID:" & Str$(ctrlID) & " [" & ctrlText & "]"
If ctrlID = myCtrlID And myCtrlText = ctrlText Then Return hChild ' found it
Long hCtrl = FindCtrlHandle(hChild, myCtrlID, myCtrlText, depth + 1)
If (hCtrl > 0) Then Return hCtrl ' no need to search further
hChild = GetWindow(hChild, %GW_HWNDNEXT)
Wend
Return 0 ' not found
End Function

'------------------------------------------------------------------
' Send text to a target window
'------------------------------------------------------------------
Function Custom_SetText( hWnd As DWord, ctrlID As Long, sText As String)
'------------------------------------------------------------------
Dim szText As Asciiz * 1024
szText = sText
Long hCtrl = FindCtrlHandle(hWnd, ctrlID)
SendMessage hCtrl, %WM_SETTEXT, 0, VarPtr(szText)
Sleep delaymsecs
End Function

'------------------------------------------------------------------
' Get text from a target window
'------------------------------------------------------------------
Function Custom_GetText( hWnd As DWord ) As String
'------------------------------------------------------------------
String sText
Long lText = SendMessage(hWnd, %WM_GETTEXTLENGTH, 0, 0) + 1 ' -- Retrieve the text length in target GUI element
sText = Repeat$(lText, $NUL) ' -- Allocate string buffer able to contain the text
SendMessage hWnd, %WM_GETTEXT, lText, StrPtr(sText) ' -- Retrieve the text
If Len(sText) > 0 Then sText = LEFT$(sText, Len(stext)-1) 'remove trailing null
Return Trim$(sText) ' -- ... and pass it out of the function
End Function

rjp74
31-08-2012, 17:08
Hi

many thanks for all your help. I really appreciate that.

I have made a first working program that can do basic object and mouse manipulation.
Part 1 does a variation of hello world! and Part 2 draws a circle using the mouse.
Nothing spectacular - but it works!

Code attached. Start Xara and then run code.
On my machine I do not need any extra sleep statements. I would be interested to know if this runs on your machine as last time it GPF'd.

7927

rjp74
31-08-2012, 20:45
Using the framework from before, I thought I would create something that uses the standard Windows 7 MSPaint.EXE application as many people may not have Xara.
Ok - it was harder than expected due to MsPaint using the ribbon and no controls are available. However, using the tree of controls and the spy program it was possible to see where the panels were. They have neither controlID or text so accessing them has to be done step by step rather than directly. Using the hWnd for the panel I can click on a button using mouse corrdinates. In this way I can still access the controls even though I cannot access then in the normal fashion.

using the attached program I then drew a spiral on msPaint like this:
7928

the program is here if you wish to see how I find the correct hWnd handles for the top panel and the canvas:
7929

Petr Schreiber
31-08-2012, 22:08
Hi,

I tried the XaraAutomation script on Windows 7, czech mutation. Originally, it fired lot of errors in Xara.

The reason was simple - on Czech Windows the decimal separator is comma, not dot.

The Format$ does not respect system setting for decimal separator.

As usually, the magician José Roca comes to help, with detection of system settings for decimal separator.

The new part is mostly this:


Type NUMBERFMT
NumDigits As DWord ' number of decimal digits
LeadingZero As DWord ' if leading zero in decimal fields
Grouping As DWord ' group size left of decimal

lpDecimalSep As DWord ' ptr to decimal separator string
lpThousandSep As DWord ' ptr to thousand separator string

NegativeOrder As DWord ' negative number ordering
End Type
Declare Function GetNumberFormat Lib "KERNEL32.DLL" Alias "GetNumberFormatA" (ByVal Locale As Long, ByVal dwFlags As DWord, lpValue As Asciiz, lpFormat As NUMBERFMT, lpNumberStr As Asciiz, ByVal cchNumber As Long) As Long

' -- Slightly modified code by José Roca
Function FormatWithLocale(ByVal xNum As Ext, _
Opt ByVal NumDigits As Long, _
ByVal ThousandSep As Long, _
ByVal LeadingZero As Long, _
ByVal NegativeOrder As Long, _
ByVal Grouping As Long) _
As String

Local lpzInputValue As Asciiz * 255
Local lpzOutputValue As Asciiz * 255
Local NF As NUMBERFMT
Local sComma As String
Local sDecimal As String

sDecimal = ","
sComma = ""
If ThousandSep Then sComma = "."
If ThousandSep > 0 Then
sComma = Chr$(ThousandSep)
If sComma = "," Then sDecimal = "."
End If

NF.lpDecimalSep = StrPtr(sDecimal)
NF.lpThousandSep = StrPtr(sComma)

If NumDigits > 0 Then NF.NumDigits = NumDigits
If LeadingZero Then NF.LeadingZero = 1
NF.NegativeOrder = 1
If NegativeOrder Then NF.NegativeOrder = 0
NF.Grouping = 3
If Grouping > 0 Then NF.Grouping = Grouping

lpzInputValue = LTrim$(Str$(xNum, 18))
GetNumberFormat( 0, 0, lpzInputValue, NF, lpzOutputValue, 255)

Function = lpzOutputValue

End Function


I attach full new code.


Petr

rjp74
01-09-2012, 00:56
Good catch!

I believe that the format$() function should take into account the system Locale settings (System.Globalization.CultureInfo) so that the functions work internationally. A way to override the default locale is also tremendously useful for when you need to send data to someone from a another locale. Mostly, it is a switch between "," and "." but there are exceptions such as in Switzerland where a single quote ' is often used as a thousands separator. This can play havoc with s/w that reads the data back in and also with SQL statements that have trouble with data containing quotes.

I would propose that your code should be incorporated into the TinyBasic format$() function as a system standard rather than being repeated everywhere in user scripts. However, until such time as this happens your code is the best way forward.

Petr Schreiber
01-09-2012, 01:09
ThinBASIC as is works in way everything is done in "English" way consistently on all systems.
It has one huge advantage - you write file with decimal numbers on Czech Windows, you can read it in English Windows without single issue.

For example in .NET, doing the same will result in crash, when not being careful enough (storing culture info as well and parsing carefully).

But I agree it would be useful to somehow elegantly integrate something like CultureInfo to ThinBASIC, in optional way. It could open doors to more easy way of writing international applications.

Regarding current situation - instead of copy/pasting the routines from code to code, it is good idea to create thinBasic Unit file. That is file, where you store various general purpose functions, types, declarations... and you simply #include the file to your script later:


#include "LocalSettings.tBasicU"
#include "WindowsMessages.tBasicU"


... this way the main code of application remains clear, without flood of various wrappers making the code harder to read.


Petr

rjp74
01-09-2012, 10:06
Its a new day and so I decided I would include the FormatWithLocale code.
However, I discovered very quickly that on my English system it does not work. it changes 1.003 into 1,003 for example.
I have vb6 code that does work in an archive somewhere. At some point I will hunt it out. Tested on apps in UK, DE, FR and CH.

I have changed the code to use #include. I am still very new to TinyBasic and did not know how to do that - so thank you for the tip.

rjp74
01-09-2012, 10:12
I wrote a small program to list out the hierarchy of a programs controls.
It is very helpful to see how to navigate to the control you need.

For example the ouput for msPaint is this:


1968250 &h1E087A - Paint
396088 &h60B38 ID: 0 [UIRibbonDockLeft]
133968 &h20B50 ID: 0 [UIRibbonDockRight]
133970 &h20B52 ID: 0 [UIRibbonDockTop]
133974 &h20B56 ID: 0 [Ribbon]
134016 &h20B80 ID: 0 [Ribbon]
658240 &hA0B40 ID: 0 []
396090 &h60B3A ID: 0 []
133966 &h20B4E ID: 0 [UIRibbonDockBottom]
461634 &h70B42 ID: 59648 []
133980 &h20B5C ID: 0 []
592694 &h90B36 ID: 50077 []
396108 &h60B4C ID: 50078 []
592674 &h90B22 ID: 1 []
396100 &h60B44 ID: 9249 []
396104 &h60B48 ID: 0 [UIRibbonWorkPane]
133978 &h20B5A ID: 59393 []
133976 &h20B58 ID: 53249 []
133938 &h20B32 ID: 53250 [Zoo&m]
133934 &h20B2E ID: 53251 [100%]
133936 &h20B30 ID: 53252 [-]
133932 &h20B2C ID: 53254 []
133920 &h20B20 ID: 53253 [+]
133914 &h20B1A ID: 59420 []
133928 &h20B28 ID: 59421 []
133924 &h20B24 ID: 59419 []
133998 &h20B6E ID: 59422 []


Uses "UI"
Uses "Console"
Declare Function GetWindow Lib "user32.dll" Alias "GetWindow" (ByVal hWnd As DWord, ByVal uCmd As Long) As Long
Declare Function GetDlgCtrlID Lib "user32.dll" Alias "GetDlgCtrlID" (ByVal hwndCtl As DWord) As Integer

'------------------------------------------------------------------
' Specify the target window name here
'------------------------------------------------------------------
String appl = "Paint"

' get top level handle
DWord hWnd = Win_FindByTitle(appl, %WIN_FINDTITLECONTAIN)
If hWnd = 0 Then
MsgBox 0, appl & " Window not found"
Stop
End If

Console_WriteLine Str$(hWnd) & " &h" & Hex$(hWnd) & " - " & appl
' list out the controls....
FindCtrlHandle(hWnd, 0, "0", 1)

MsgBox(0, "done")
Stop

'------------------------------------------------------------------
' Search for a control ID inside a main window
'------------------------------------------------------------------
Function FindCtrlHandle(hWnd As DWord, myCtrlID As Long, myCtrlText As String, depth ) As DWord
'------------------------------------------------------------------
Long hChild = GetWindow(hWnd, %GW_CHILD)
While hChild
String ctrlText = Custom_GetText(hChild)
Long ctrlID = GetDlgCtrlID(hChild)
Console_WriteLine String$(depth * 3, " ") & Str$(hChild) & " &h" & Hex$(hChild)& " ID:" & Str$(ctrlID) & " [" & ctrlText & "]"
'If ctrlID = myCtrlID And myCtrlText = ctrlText Then Return hChild ' found it
Long hCtrl = FindCtrlHandle(hChild, myCtrlID, myCtrlText, depth + 1)
'If (hCtrl > 0) Then Return hCtrl ' no need to search further
hChild = GetWindow(hChild, %GW_HWNDNEXT)
Wend
Return 0 ' not found
End Function
'------------------------------------------------------------------
' Send text to a target window
'------------------------------------------------------------------
Function Custom_SetText( hWnd As DWord, ctrlID As Long, sText As String)
'------------------------------------------------------------------
Dim szText As Asciiz * 1024
szText = sText
Long hCtrl = FindCtrlHandle(hWnd, ctrlID)
SendMessage hCtrl, %WM_SETTEXT, 0, VarPtr(szText)
Sleep delaymsecs
End Function
'------------------------------------------------------------------
' Get text from a target window
'------------------------------------------------------------------
Function Custom_GetText( hWnd As DWord ) As String
'------------------------------------------------------------------
String sText
Long lText = SendMessage(hWnd, %WM_GETTEXTLENGTH, 0, 0) + 1 ' -- Retrieve the text length in target GUI element
sText = Repeat$(lText, $NUL) ' -- Allocate string buffer able to contain the text
SendMessage hWnd, %WM_GETTEXT, lText, StrPtr(sText) ' -- Retrieve the text
If Len(sText) > 0 Then sText = LEFT$(sText, Len(stext)-1) 'remove trailing null
Return Trim$(sText) ' -- ... and pass it out of the function
End Function

Petr Schreiber
02-09-2012, 16:33
Hi,

I investigated a bit the system decimal separator detection, you can use FormatStrLocale to perform Format$ with system settings aware mode:


' -- Equates
%LOCALE_USER_DEFAULT = &h0400
%LOCALE_SYSTEM_DEFAULT = &h0800
%LOCALE_SDECIMAL = &h0000000E

' -- API function to retrieve locale info (declare by José Roca)
Declare Function GetLocaleInfo Lib "KERNEL32.DLL" Alias "GetLocaleInfoA" ( _
ByVal Locale As DWord _ ' __in LCID Locale
, ByVal LCType As DWord _ ' __in LCTYPE LCType
, ByRef lpLCData As Asciiz _ ' __out LPSTR lpLCData
, ByVal cchData As Long _ ' __in int cchData
) As Long

' -- Function for system aware formatting
Function FormatStrLocale(nValue As Ext, sMask As String) As String

Dim userDecimal As Asciiz * 32
GetLocaleInfo(%LOCALE_SYSTEM_DEFAULT, %LOCALE_SDECIMAL, userDecimal, SizeOf(userDecimal))

String sResult = Format$(nValue, sMask)
sResult = Replace$(sResult, ".", userDecimal)

Return sResult

End Function


Usage is like:


String sConvertedValue = FormatStrLocale(1.123, "0.00")

' -- Will result in 1.12 on EN Windows
' -- Will result in 1,12 on CZ Windows



Petr