1

Тема: VBS,HTA: Скрипты вне окна и внутри его

Плотно и часто использую динамическое создание окна HTA из скрипта VBS, почерпнутое мной из темы JScript: Создание окна, а также трансляция WScript в HTA. Умею только на VBS, поэтому использую конкретно этот вариант.
В текущий момент стоит задача проверить корректную читаемость нескольких тысяч файлов в формате ai (Adobe Illustrator).
Всё хорошо до тех пор, пока Illustrator не открывает диалоговое окошко с каким-нибудь вопросом или сообщением. В этом случае всё останавливается и ждёт, пока пользователь отреагирует.
Возникла идея использовать имитацию нажатия клавиш (метод SendKeys из библиотеки WScript.Shell).
Для того, чтобы всё сработало как надо, необходимо перед попыткой открытия файла отложенно запустить функцию с проверкой, не висит ли скрипт, и с необходимыми действиями по имитации нажатия клавиш (метод SetTimeout).
Однако, данный метод работает только в "оконном VBS". После экспериментов выяснилось, что если внести нужную функцию в текст, передаваемый в функцию динамического открытия окна HTA, то эту функцию можно вызвать с помощью метода SetTimeout. Простейшая проверка (отображение номера файла) показала работоспособность такого вызова: при нормальном открытии файлов функция, которая вызывается отложенно, отображает номера файлов с запаздыванием на несколько единиц, а когда открывается диалоговое окно и скрипт замирает, номера доходят до текущего.
Но тут появляется новая сложность: если метод SetTimeout доступен только в "оконном VBS", то объект WScript в нём никак невозможен.
В связи с этим вопрос: возможно ли как-то из "оконного VBS" обратиться к функциям из главного скрипта, породившего это окно? Или ещё какой-то способ?
В качестве запасного варианта держу возможность дополнительного скрипта, который бы запускался из функции, вызываемой методом SetTimeout, но хотелось бы по возможности обойтись одним скриптом.

2

Re: VBS,HTA: Скрипты вне окна и внутри его

Необходимый код в контексте окна можно выполнить после его создания, используя window.execScript(). Это позволит и добавить функцию, и вызвать setTimeout(), из контекста скрипта VBS.

Я бы все же попробовал разместить целевую функцию в коде скрипта VBS, передать Me скрипта в качестве свойства window, а по таймауту запускать короткую функцию-переходник, которая бы просто вызывала целевую функцию через Me.MyFunction(). Либо, в качестве альтернативы, передать в setTimeout ссылку на целевую функцию, полученную через GetRef().

Кстати, вот такое решение может Вам показаться интересным.

Щт Уккщк Куыгьу Туче
’ҐЄгй п Є®¤®ў п бва Ёж : 1251

3 (изменено: fy73, 2017-11-25 15:53:49)

Re: VBS,HTA: Скрипты вне окна и внутри его

Спасибо, не всё понял, но буду экспериментировать.
SetTimeout из основного скрипта мне удалось вызвать в форме Win.SetTimeout, где Win - переменная, ссылающаяся на созданное окно (возвращаемая функцией CreateWindow). При этом запустить отложено таким образом можно только функцию, которая описана в "оконном" VBS.
---
Всё отлично получилось.
В текст открываемого окна добавил описание переменной Main, затем после открытия окна

Set Win.Main=Me

И прекрасно работает конструкция

Win.SetTimeout "Main.Control ",5000,"VBScript"

Где Control - функция, описанная в основном скрипте.
Ещё раз большое спасибо. Теперь можно полностью пользоваться преимуществами обеих форм VBS, да ещё и (видимо) распараллеливать вычисления.

4

Re: VBS,HTA: Скрипты вне окна и внутри его

fy73 пишет:

... И прекрасно работает конструкция

Win.SetTimeout "Main.Control ",5000,"VBScript"

Чтож, вполне себе решение. Однако, я считаю, что разбрасывать код по контекстам - не есть лучшая практика, и в контексте HTA его должен быть минимум, поэтому подразумевал что-то навроде такого кода в скрипте VBS:

Win.SetTimeout GetRef("Control"), 5000

Sub Control
    ' здесь код
Sub

В этом случае передавать Me нет необходимости.

fy73 пишет:

... да ещё и (видимо) распараллеливать вычисления

В общем-то это возможно, поскольку в Вашем случае запущено 2 процесса. Но перед забегом по граблям советую почитать это и это. Относительно годное решение я предлагал тут, правда, это было давно, с тех пор мне стало очевидно, что код может быть значительно улучшен.

Щт Уккщк Куыгьу Туче
’ҐЄгй п Є®¤®ў п бва Ёж : 1251

5 (изменено: fy73, 2017-11-26 22:54:00)

Re: VBS,HTA: Скрипты вне окна и внутри его

По граблям побегать немножко успел, получилось, что конкретно в моей реализации параллельности не получилось. Сначала начала выполняться функция, идущая следом за отложенным вызовом, потом, когда подходит время для отложенной функции, начинает выполняться она, а первая приостанавливается, а уже после завершения отложенной продолжает работу первая.
Насчёт разброса по контекстам - да, неудобно, особенно если одну и ту же функцию вызываешь и так, и так.
Спасибо за очередную пищу для размышлений, буду изучать и пробовать.
---
К сожалению, GetRef не даёт передать аргументы, что не очень удобно.

6

Re: VBS,HTA: Скрипты вне окна и внутри его

Приветствую, господа. Почитал тему и задумался. Как-то всё сумбурно. Непонятно что для чего и куда (ну по крайней мере мне).

Решил начать по пунктам с тем с чем не согласен

fy73 пишет:

Но тут появляется новая сложность: если метод SetTimeout доступен только в "оконном VBS", то объект WScript в нём никак невозможен.

Ну почему же недоступен ? Если очень нужно, то доступен.


Option Explicit

Dim objUDF
Dim intIntervalID
Dim intTimeoutID
Set objUDF = new clsUDF

RunInConsoleMode()

intIntervalID = objUDF.SetInterval(GetRef("Interval_Test"), 1000)

intTimeoutID = objUDF.SetTimeout(GetRef("Timeout_Test"), 5000)

Sub Interval_Test()
	WScript.Echo "Sub Interval_Test called. " & Time
End Sub

Sub Timeout_Test()
	objUDF.ClearInterval intIntervalID
	objUDF.ClearTimeout intTimeoutID
	WScript.Echo "Timeout reached !"
End Sub

WScript.Sleep 6000

WScript.Echo "Complete.... Please, press <ENTER>"

WScript.StdIn.ReadLine

Class clsUDF
	Private document,_
			window
	
	Private Sub Class_Initialize()
		Set document = CreateObject("htmlfile")
		Set window = document.parentWindow
	End Sub
	
	Public Function SetTimeout(object, timeout)
		SetTimeout = window.setTimeout(object, timeout)
	End Function

	Public Function SetInterval(object, timeout)
		SetInterval = window.setInterval(object, timeout)
	End Function

	Public Sub ClearInterval(intervalId)
		window.clearInterval intervalId
	End Sub
	
	Public Sub ClearTimeout(timeoutId)
		window.clearTimeout timeoutId
	End Sub
	
End Class

Sub RunInConsoleMode()
	If InStr(1,WScript.FullName,"cscript",1) <= 0 Then 
		CreateObject("WScript.Shell").Run("cscript /nologo """ & WScript.ScriptFullName & """")
		WScript.Quit
	End If
End Sub
fy73 пишет:

К сожалению, GetRef не даёт передать аргументы, что не очень удобно.

Ну в явном виде нет, но если немного покреативить, то опять же всё возможно:


Option Explicit

Dim strLog, iReq

RunInConsoleMode()

RequestURL "http://www.planet-source-code.com"
RequestURL "http://www.google.ru"
RequestURL "http://www.rambler.ru"
RequestURL "http://www.yandex.ru"
RequestURL "http://www.vesti.ru"

Sub RequestURL(url)
	Dim objXHR, objCaller
	iReq = iReq + 1
	Set objCaller = new clsCaller
	Set objXHR = CreateObject("MSXML2.ServerXMLHTTP.6.0")
	With objCaller
		.Args = Array(objXHR, Now, url)
		Set .callback =  GetRef("xhr_onreadystatechange")
	End With
	WScript.Echo "Requesting """ & url & """"
	With objXHR
		.open "GET", url, true
		.setRequestHeader "Cache-Control", "no-cache"
		.setRequestHeader "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"
		.onreadystatechange = objCaller
		.send
	End With
End Sub

Sub xhr_onreadystatechange(objXHR, requestStarted, url)
	Dim strReadyStateText
	Select Case objXHR.readyState
	Case 0
		strReadyStateText = "Unsent"
	Case 1
		strReadyStateText = "Opened"
	Case 2
		strReadyStateText = "Headers received"
	Case 3
		strReadyStateText = "Loading"
	Case 4
		strReadyStateText = "Done"
	End Select
	WScript.Echo strReadyStateText & " " & url & " " & FormatDateTime(Now - requestStarted, vbLongTime)
	if objXHR.readyState <> 4 Then Exit Sub
	iReq = iReq - 1
End Sub

While iReq > 0
	WScript.Sleep 100
Wend

WScript.Echo "Complete.... Please, press <ENTER>"

WScript.StdIn.ReadLine

Class clsCaller
	Public callback, args
	Public Default Function [call]()
		Dim code, i
		For i = Lbound(args) to Ubound(args)
			code = code & ", args(" & i & ")"
		Next
		Stub Eval("callback(" & mid(code,2) & ")"), [call] 
	End Function

	Private Sub Stub(p1, p2)
		If isObject(p1) Then
			Set p2 = p1
		Else
			p2 = p1
		End If
	End Sub
End Class

Sub RunInConsoleMode()
	If InStr(1,WScript.FullName,"cscript",1) <= 0 Then 
		CreateObject("WScript.Shell").Run("cscript /nologo """ & WScript.ScriptFullName & """")
		WScript.Quit
	End If
End Sub
Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

7

Re: VBS,HTA: Скрипты вне окна и внутри его

Большое спасибо.
Теперь у меня ещё больше материала для изучения.
Я, в общем-то, чайник, поэтому использую простые методы и те, которые нахожу здесь, например.
То, ради чего всё это затевалось, уже почти работает в форме

...
Set Win.Main=Me
...
Win.SetTimeout "Main.Control ",5000,"VBScript"
...

Но в любом случае изучу приведённые примеры.
Ещё раз спасибо.