I'm using AutoIt to automate filling out a form in a 3rd party .NET application. I'm having trouble setting the date in a calendar control.
The control is of class type WindowsForms10.SysDateTimePick32.app.0.378734a
, according to AutoIt3 Window Info Tool. Initially I just tried setting the text of the control to my desired date, but this didn't do anything at all.
Then I tried using _GUICtrlMonthCal_SetCurSel
, but (as with all _GUI*
functions I tried) this didn't do anything either (the correct header was included, it just didn't do anything when I ran it).
Does anyone have any ideas how I would go about doing this?
The reason you can't use the GUICtrlMonthCal* functions is because you are not dealing with a month/calender control. In .NET they do have them, but in this case it's a DateTimePicker instead.
DateTimePickers do have a MonthCalender in the drop down though, but it would appear this is created when you click the drop down so it is difficult to control directly. Instead I took a look at what happens when the program sets the value by setting up a small test case... These were the messages I found:
<00001> 00090572 S WM_GETTEXTLENGTH
<00002> 00090572 R WM_GETTEXTLENGTH cch:12
<00003> 00090572 S WM_GETTEXT cchTextMax:26 lpszText:05E8D51C
<00004> 00090572 R WM_GETTEXT cchCopied:12 lpszText:05E8D51C ("0")
<00005> 00090572 S message:0x1002 [User-defined:WM_USER+3074] wParam:00000000 lParam:01F6A2A8
<00006> 00090572 R message:0x1002 [User-defined:WM_USER+3074] lResult:00000001
<00007> 00090572 S WM_GETTEXTLENGTH
<00008> 00090572 R WM_GETTEXTLENGTH cch:12
<00009> 00090572 S WM_GETTEXT cchTextMax:26 lpszText:05E8D51C
<00010> 00090572 R WM_GETTEXT cchCopied:12 lpszText:05E8D51C ("0")
<00011> 00090572 P WM_PAINT hdc:00000000
<00012> 00090572 S WM_ERASEBKGND hdc:54010EE3
<00013> 00090572 R WM_ERASEBKGND fErased:True
The most interesting there is the user-defined message, which is how controls usually get messages that are unique to them. If we assume that I am right that 0x1002 sets the data, then the only thing to still be done is see what lParam means. This could be tricky as you set the value as a DateTime object.
The next thing I would do is check ildasm to have a look at System.Windows.Forms.DateTimePicker::set_Value as I imagine that would tell you lot's about how .NET does it... I'll do some more research and update this post.
Part 2: Ok, so the disassembly for set_Value does what I said it would... It converts the DateTime to something they call "System" time using DateTimeToSysTime... Which although not publicly visible, you can see roughly what it's doing in ILDasm... But it's easier to simply assume it's using the standard WinAPI SYSTEMTIME structure here. It's a fair bit of reading, but you need to fill one of those structs.
We can then make the fairly safe assumption that the lParam in the message we want is a pointer to the SYSTEMTIME struct... I'll test it out and update the post :)
Part 3: Now for the exciting part... Getting it to work in practice. The first problem is that we can't move pointers between applications using SendMessage, so we need a bit of extra code to create a buffer in the other program. Other than that, it worked exactly like I expected:
#Include <GuiMonthCal.au3> ; $tagSYSTEMTIME is defined in here.
Local $tSI = DllStructCreate($tagSYSTEMTIME)
Local $hControl = ControlGetHandle("Date/Time Picker", "", "[NAME:ExampleDateTimePicker]")
If @error Then Exit 0 * MsgBox(16, "Error", "Demo control not found.")
; Fill the structure to current date/time using GetLocalTime
DllCall("kernel32.dll", "none", "GetLocalTime", "ptr", DllStructGetPtr($tSI))
If @error Then Exit 0 * MsgBox(16, "Error", "GetLocalTime failed.")
; Change the year
DllStructSetData($tSI, "Year", 2005)
; The struct needs to be in the process memory, so it's a bit of a workaround.
Local $tMemMap
Local $pMemory = _MemInit($hControl, DllStructGetSize($tSI), $tMemMap)
_MemWrite($tMemMap, DllStructGetPtr($tSI))
$iRet = _SendMessage($hControl, 0x1002, 0, $pMemory, 0, "wparam", "ptr")
_MemFree($tMemMap)
When you run that, the date part should change to 2005. The only problem is that it does not trigger the OnValueChanged event, so if there is any handler code for that in the program you are trying to automate then it may not work exactly as it would if the user changed the value.