Тема: HTA: аналог DoEvents (обновление информации из работающего скрипта)
Я в последнее время разбирался с возможностью вывода информации на форму HTA из работающего скрипта и, в итоге, обнаружил интересную особенность у метода .Run объекта 'WScript.Shell'. Когда из HTA через Run запускается с ожиданием какой-нибудь процесс, на форме HTA обновляется информация и работают (с некоторыми ограничениями) элементы управления. Соответственно, чтобы информация на форме обновлялась, нужно регулярно запускать через Run какой-нибудь процесс, который будет быстро закрываться.
Сразу приведу небольшой поясняющий пример:
<HTML> <META http-equiv="Content-Type" content="text/html; charset=windows-1251">
<META http-equiv="MSThemeCompatible" content="Yes">
<HEAD>
<TITLE>wshSheel Run demo</TITLE>
<HTA:APPLICATION ID="oHTA" APPLICATIONNAME="Demo" BORDER="thick"
BORDERSTYLE="normal" CAPTION="yes" CONTEXTMENU="no" INNERBORDER="no"
MAXIMIZEBUTTON="no" MINIMIZEBUTTON="yes" NAVIGABLE="no" SCROLL="no"
SCROLLFLAT="no" SELECTION="yes" SHOWINTASKBAR="yes" SINGLEINSTANCE="no"
SYSMENU="yes" WINDOWSTATE="normal" VERSION="1.0"
>
</HEAD>
<BODY bgcolor="buttonface" onload="bodyOnLoad();">
строка1:<INPUT ID="txt1" type="text" size="30" value="str1">
строка2:
<INPUT type="button" value="..." title="Можно выбрать каталог" onclick="chooseFolder( event );" style="font-weight: bold; ">
<INPUT ID="txt2" type="text" size="60" value="str 2">
<BR><BR>
<INPUT type="button" value="Запустить цикл" onclick="startD();">
<INPUT type="button" value="Остановить цикл" onclick="stopD();">
<INPUT type="button" value="Кнопка 1" onclick="but( 1 );">
<INPUT type="button" value="Кнопка 2" onclick="but( 2 );">
<INPUT type="button" value="Кнопка 3" onclick="but( 3 );">
<BR>
<INPUT ID="chkUnload" type="checkbox"> Не показывать запрос при закрытии
<BR>
I = <SPAN ID='spanCounter'></SPAN>
<BR>
<TEXTAREA ID="taOut" cols="100" rows="28"></TEXTAREA>
</BODY>
</HTML>
<SCRIPT language="JavaScript"><!--
function bodyOnLoad(){
window.moveTo( 50, 50 );
window.resizeTo( 900 , 600 );
window.onbeforeunload = beforeUnload;
out( 'Запуск Demo' );
out( '' );
}
function beforeUnload(){
if( chkUnload.checked ){
stopD();
}else{
if( runFlag ){
return 'Цикл ещё запущен. Нажмите "Отмена" и остановите выполнение цикла, иначе приложение останется в памяти.';
}
}
}
var runFlag;
function stopD(){
runFlag = false;
}
function startD(){
var wshShell = new ActiveXObject( 'WScript.Shell' );
out( 'Цикл запущен' );
out( '' );
runFlag = true;
for( var i = 0 ; runFlag ; ++i ){
out( 'i = ' + i + ' строка1: ' + txt1.value + ' строка2: ' + txt2.value );
spanCounter.innerText = i;
wshShell.Run( 'ping 127.255.255.255 -n 1 -w 2000' , 0 , true );
}
out( '' );
out( 'Цикл остановлен' );
out( '' );
}
function but( num ){
out( 'Нажата кнопка ' + num );
}
function out( Str ){
taOut.value = (new Date()).toLocaleString() + ': ' + Str + '\n' + taOut.value;
}
function chooseFolder( event ){
var objFolder = (new ActiveXObject( 'Shell.Application' )).BrowseForFolder( 0 , 'Выберите папку' , 0x50 , 17 );
if( objFolder !== null ){
txt2.value = objFolder.Items().Item().Path;
}
}
// --></SCRIPT>В этом примере при нажатии кнопки "Запустить цикл" запустится цикл, в котором вызывается Shell.Run и обновляется информация на форме. Идея примера в том, что можно запустить цикл, понажимать на кнопки, ввести что-нибудь в текстовые поля и посмотреть на то, как это отразится на форме.
Я попробовал несколько разных вариантов вызова метода Run, и проверил несколько ОС -Win Xp, Server 2003, Win 7, Server 2008 R2.
На текущий момент выяснил следующее:
1. Регулярный вызов .Run( ‘ping /?’ , 0 , true ) обновляет форму на всех 4 ОС, которые я проверял.
2. При таком варианте обновления формы не работает переход между элементами управления по TAB, хотя информация в текстовые поля вводится и нажатия на кнопки мышью отслеживаются.
3. Когда скрипт запущен нажатием на кнопку – повторное нажатие на эту-же кнопку не срабатывает.
4. Если закрыть приложение не завершив цикл с doEvents, процесс mshta.exe останется в памяти, даже если повесить свой обработчик на onbeforeunload и останавливать цикл из обработчика. В этом случае вызовы Shell.Run прекращаются, но mshta всё равно остаётся в памяти.
5. В Win7 и Server 2008 R2 форма так-же обновляется при выполнении следующей команды: try{ wshShell.Run( '/' , 0 , false ) }catch( e ){}; . При использовании этого варианта, если закрыть приложение с запущенным скриптом и из обработчика onbeforeunload установится флаг прекращения работы – скрипт останавливается и процесс mshta.exe выгружается из памяти.
Пока, как результат этих опытов, я написал вариант объекта для обновления формы (приведён ниже). Объект создаётся конструктором DoEventsFunc, конструктор возвращает объект-функцию, которую нужно вызывать, когда необходимо обновление формы и реакция на элементы управления. Конструктору может быть передано 2 параметра - минимальный промежуток между вызовами wshShell.Run и режим работы – быстрый ( с wshShell.Run( '/' , … ) ) или обычный. Если режим работы не указан – он выбирается исходя из версии ОС.
Так же у объекта есть два метода - .set( aDelay) - изменяет промежуток между вызовами Run во время работы и .isFast() - позволяет узнать режим работы (быстрый / обычный).
/*
DoEventsFunc - объект для обновления информации в окне приложения.
Исходно в HTA информация на странице не обновляется, пока не закончится
выполнение работающего кода. Данный объект позволяет из работающего скрипта
обновить информацию на странице и произвести реакцию на возможные события
(нажатие кнопок, перемещение окна). Работа объекта основана на том, что
при запуске из HTA приложений через метод Run объекта 'WScript.Shell'
обновляется информация на форме HTA и запускается цикл обработки событий.
бработать за счёт того, что вся страница перерисовывается в случае, если,
даже программно, скролится какой-либо элемент.
Для работы объекта на странице должен быть размещён DIV, HTML код которого
приведён в комметарии внутри тела объекта, а ссылка на этот DIV должна быть
передана либо при создании объекта, либо позже, вызовом функции Set
пример: RefreshForm.Set( document.getElementById( 'pRefresh') );
Конструктору DoEventsFunc может быть передано 2 параметра:
aDelay - минимальный промежуток между вызовами wshShell.Run(), чтобы
вызовы не происходили слишком часто. По умолчанию - 100 мс.
fastRun - выбор режима работы.
fastRun = false - обычный режим, более медленный и с особенностями, которые
могут привести к тому, что процесс mshta.exe останется в
память, но работающий на всех опробованных ОС.
fastRun = true - быстрый режим, меньше грузит систему, mshta.exe в памяти
не остаётся, но пока работает только на Win7 и Server 2008 R2.
Если fastRun не указан или undefined - режим выбирается исходя из версии ОС
У объекта есть кроме непосредственного вызова есть два метода:
.set(aDelay) - устанавливает задержку.
.isFast() - позволяет узнать режим работы.
//*/
function DoEventsFunc( aDelay , fastRun ){
var wshShell = new ActiveXObject( 'WScript.Shell' );
var delay = 100 , lastDate = 0 ;// задержка по умолчанию, мс.
var runStr , runWait , fastMode; // Параметры вызова wshShell.Run.
var fastOS = false;
try{ //определяем версию ОС.
if( new Enumerator(
GetObject( 'winmgmts:')
.ExecQuery( 'Select * from Win32_OperatingSystem' )
).item().Version.match( /^6\.1/ )
){
fastOS = true;
}
}catch( e ){
}
// Выбираем режим работы.
if( ( typeof fastRun === 'undefined' && fastOS ) || fastRun ){
runStr = '/'; // настройки быстрого режима.
runWait = false;
fastMode = true;
}else{
runStr = 'ping.exe /?'; // настройки для обычного режима.
runWait = true;
fastMode = false;
}
// RetFunc - функция, которая вернётся как результат вызова функции, создающей
// объект и которая будет вызывать обновление формы.
var retFunc = function(){
var t = new Date();
if( t - lastDate > delay ){
lastDate = t;
try{ wshShell.Run( runStr , 0 , runWait ) }catch( e ){};
}
};
// функция Set - изменение задержки между вызовами.
retFunc.set = function( aDelay ){ delay = parseInt( aDelay , 10 ); };
// функция isFast - получение режима работы.
retFunc.isFast = function(){ return fastMode; };
if( typeof aDelay !== 'undefined' ) retFunc.set( aDelay );
return retFunc;
};Пример использования объекта – приложение для поиск файлов, заданных маской - регулярным выражением по дереву каталогов. В этом примере объект doEvents создаётся в функции-обработчике события onload – bodyOnLoad, вызывается в функции outStatus(). Также нужно посмотреть функцию – обработчик события onbeforeunload - beforeUnloadFunc().
<!--
Поиск файлов в дереве каталогов с использованием регулярного выражения.
Пример использования эмулятора DoEvents.
Copyright (C) 2013 Valery Chernysh (spy00000@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<HTML> <META http-equiv="Content-Type" content="text/html; charset=windows-1251">
<META http-equiv="MSThemeCompatible" content="Yes">
<HEAD>
<TITLE>DoEvents Example</TITLE>
<HTA:APPLICATION ID="oHTA" APPLICATIONNAME="Exam" BORDER="thick"
BORDERSTYLE="normal" CAPTION="yes" CONTEXTMENU="no" INNERBORDER="no"
MAXIMIZEBUTTON="no" MINIMIZEBUTTON="yes" NAVIGABLE="no" SCROLL="no"
SCROLLFLAT="no" SELECTION="yes" SHOWINTASKBAR="yes" SINGLEINSTANCE="no"
SYSMENU="yes" WINDOWSTATE="normal" VERSION="1.0"
>
<STYLE type="text/css">
.Tab {height:100%; width:100%; background-color: #000000; font-family: Courier New; border:1px solid black;}
.Tab2 {height:100%; width:100%; background-color: buttonface;}
TD {background-color: buttonface; color: buttontext; font-weight: bold; }
INPUT {font-weight: bold; }
.Status {background-color: #C0C0FF; font-weight: bold; }
.logSpan {width:100%; font-family: Courier New;}
.logOk {background-color: #C0FFC0;}
.logErr {background-color: #FFC0C0;}
.logWarn {background-color: #FFFFC0;}
.logLog {background-color: #C0C0FF;}
</STYLE>
</HEAD>
<BODY bgcolor="buttonface" onload="bodyOnLoad()">
<TABLE class="Tab">
<TR>
<TD>Шаблон:<INPUT ID="txtRE" type="text" size="20" value=".*\.txt$" title="шаблон для поиска"></TD>
<TD style="width:100%;">
<TABLE class="Tab2">
<TR>
<TD style="width:10px;">Каталог:</TD>
<TD style="width:10px;">
<INPUT type="button" value="..." title="Выбрать каталог" onclick="chooseFolder( event )">
</TD>
<TD style="width:100%;">
<INPUT ID="txtFolder" type="text" style="width:100%">
</TD>
</TR>
</TABLE>
</TD></TR>
<TR>
<TD style="padding: 5px;">
<INPUT type="button" value=" Начать поиск " onclick="startSearch()">
<BR>
<INPUT type="button" value="Прекратить поиск" onclick="stopSearch()">
</TD>
<TD style="height:10px; width:100%;" class="Status">
<DIV ID="divStatus" class="Status" style="width:100%;">Выберите каталог</DIV>
</TD>
</TR>
<TR><TD style="height:100%; width:100%;" colspan="2">
<DIV ID='divLog' style="overflow:auto; width: 100%; height: 100%;"></DIV>
</TD></TR>
</TABLE>
</BODY>
</HTML>
<SCRIPT language="JavaScript"><!--
// Функция представлющая данные об ошибке в виде строки
Error.prototype.toStr = function( delim ){
return 'N='+this.number.toString(16)+'('+(this.number & 0xFFFF)+')'
+((typeof(delim)==='undefined')?' ':delim)+'D='+this.description;
}
// Глобальные переменные
// FSO , sysShell - стандартные объекты для работы
// log - объект для вывода информации на экран.
// doEvents - объект для демонстрации работы котрого и написан этот пример.
// stopSearchFlag - флаг, показывающий, что поиск нужно прекратить.
var FSO , sysShell , log , doEvents , stopSearchFlag = true;
function bodyOnLoad(){
try{
//Стандартные объекты для работы скрипта
FSO = new ActiveXObject( 'Scripting.FileSystemObject' );
sysShell = new ActiveXObject( 'Shell.Application' );
// Создаём объект для вывода информации на экран и передаём ссылку на DIV который
// будет отображать информацию.
log = LogFunc( divLog );
// Объект для обновления информации из работающего скрипта.
doEvents = DoEventsFunc();
// doEvents = DoEventsFunc( 100 , true );
// устанавливаем обработчик проверки закрытия приложения при работающем поиске.
window.onbeforeunload = beforeUnloadFunc;
}catch( e ){
// В случае ошибки выводим информацию об ошибке на экран в виде простого сообщения
// в дальнейшем информацию об ошибке можно будет выводить в файл и в список
alert( 'Ошибка при инициализации программы.\n\n' + e.toStr('\n') );
window.close();
return;
}
log( '' );
log( '-----------------------' );
log( 'Запуск' );
}
// Функция, для реакции на закрытие приложения при работающем поиске.
// В быстром режиме поиск просто останавливается, в обычном режиме -
// HTA передаётся строка которая будет показана пользователю.
function beforeUnloadFunc(){
if( ! stopSearchFlag ){
if( doEvents.isFast() ){
stopSearch();
}else{
return '!!! Поиск файлов не остановлен !!!\nПожалуйста нажмите кнопку "Отмена" и потом нажмите "Прекратить поиск", иначе приложение останется в памяти.';
}
}
return;
}
// функция остановки поиска - устанавливается флаг, что поиск нужно прекратить.
function stopSearch(){
stopSearchFlag = true;
}
// startSearch - основная функция поиска.
// проверяем началый каталог и шаблон, затем запускаем рекурсивный поиск.
function startSearch(){
if( txtFolder.value == '' ){
alert( outStatus( 'Не выбран каталог' ) );
return;
}
try{
var folder = FSO.GetFolder( txtFolder.value );
}catch( e ){
log( 2 , 'Ошибка при доступе к каталогу "' + txtFolder.value + '".\n' + e.toStr('\n') );
return;
}
try{
var RE = (new RegExp()).compile( txtRE.value , 'i' );
}catch( e ){
log( 2 , 'Шаблон поиска составлен неверно.\n' + e.toStr('\n') );
return;
}
log( 0 , '' );
log( 0
, 'Поиск по шаблону "' + txtRE.value
+ '" начиная с каталога "' + txtFolder.value + '"'
);
log( 0 , '' );
stopSearchFlag = false;
if( req( folder ) ){
log( 1 , '' );
log( 1 , outStatus( 'Поиск прекращён' ) );
}else{
log( 0 , '' );
log( 0 , outStatus( 'Поиск закончен' ) );
}
function req( folder ){ // рекурсивная функция поиска файлов по маске
if( stopSearchFlag ){ // если нужно прекращать поиск - сразу выходим
return true;
}
outStatus( folder.Path );
var fc , i = 1;
for( fc = new Enumerator( folder.Files ) ; ! fc.atEnd() ; fc.moveNext() , ++i ){
if( fc.item().Name.match( RE ) ){
log( fc.item().Path );
}
if( i % 100 === 0 ){
if( stopSearchFlag ){
return true;
}
outStatus( folder.Path + ' - ' + i + ' файлов');
}
}
for( fc = new Enumerator( folder.SubFolders ) ; ! fc.atEnd() ; fc.moveNext() ){
if( fc.item().Name.match( RE ) ){
log( fc.item().Path );
}
try{
if( req( fc.item() ) )
return true;
}catch( e ){
log( 1
, 'Ошибка при доступе к каталогу "' + fc.item().Path + '".\n'
+ e.toStr('\n')
);
}
}
return false;
//*/
}
}
// chooseFolder( event ) - показ диалога выбора каталога
function chooseFolder( event ){
var objFolder = sysShell.BrowseForFolder( 0
, 'Выберите папку для поиска файлов'
, 0x50
, 17
);
if( objFolder != null ){
txtFolder.value = objFolder.Items().Item().Path;
}
}
// outStatus( Str ) - вывод информации в строку статуса и обновление формы
function outStatus( Str ){
divStatus.innerText = Str;
doEvents();
return Str;
}
// outStatus( log( 'Синхронизация начата') );
/*
DoEventsFunc - объект для обновления информации в окне приложения.
Исходно в HTA информация на странице не обновляется, пока не закончится
выполнение работающего кода. Данный объект позволяет из работающего скрипта
обновить информацию на странице и произвести реакцию на возможные события
(нажатие кнопок, перемещение окна). Работа объекта основана на том, что
при запуске из HTA приложений через метод Run объекта 'WScript.Shell'
обновляется информация на форме HTA и запускается цикл обработки событий.
бработать за счёт того, что вся страница перерисовывается в случае, если,
даже программно, скролится какой-либо элемент.
Для работы объекта на странице должен быть размещён DIV, HTML код которого
приведён в комметарии внутри тела объекта, а ссылка на этот DIV должна быть
передана либо при создании объекта, либо позже, вызовом функции Set
пример: RefreshForm.Set( document.getElementById( 'pRefresh') );
Конструктору DoEventsFunc может быть передано 2 параметра:
aDelay - минимальный промежуток между вызовами wshShell.Run(), чтобы
вызовы не происходили слишком часто. По умолчанию - 100 мс.
fastRun - выбор режима работы.
fastRun = false - обычный режим, более медленный и с особенностями, которые
могут привести к тому, что процесс mshta.exe останется в
память, но работающий на всех опробованных ОС.
fastRun = true - быстрый режим, меньше грузит систему, mshta.exe в памяти
не остаётся, но пока работает только на Win7 и Server 2008 R2.
Если fastRun не указан или undefined - режим выбирается исходя из версии ОС
У объекта есть кроме непосредственного вызова есть два метода:
.set(aDelay) - устанавливает задержку.
.isFast() - позволяет узнать режим работы.
//*/
function DoEventsFunc( aDelay , fastRun ){
var wshShell = new ActiveXObject( 'WScript.Shell' );
var delay = 100 , lastDate = 0 ;// задержка по умолчанию, мс.
var runStr , runWait , fastMode; // Параметры вызова wshShell.Run.
var fastOS = false;
try{ //определяем версию ОС.
if( new Enumerator(
GetObject( 'winmgmts:')
.ExecQuery( 'Select * from Win32_OperatingSystem' )
).item().Version.match( /^6\.1/ )
){
fastOS = true;
}
}catch( e ){
}
// Выбираем режим работы.
if( ( typeof fastRun === 'undefined' && fastOS ) || fastRun ){
runStr = '/'; // настройки быстрого режима.
runWait = false;
fastMode = true;
}else{
runStr = 'ping.exe /?'; // настройки для обычного режима.
runWait = true;
fastMode = false;
}
// RetFunc - функция, которая вернётся как результат вызова функции, создающей
// объект и которая будет вызывать обновление формы.
var retFunc = function(){
var t = new Date();
if( t - lastDate > delay ){
lastDate = t;
try{ wshShell.Run( runStr , 0 , runWait ) }catch( e ){};
}
};
// функция Set - изменение задержки между вызовами.
retFunc.set = function( aDelay ){ delay = parseInt( aDelay , 10 ); };
// функция isFast - получение режима работы.
retFunc.isFast = function(){ return fastMode; };
if( typeof aDelay !== 'undefined' ) retFunc.set( aDelay );
return retFunc;
};
//
// Объект Log - вывод логов.
//
function LogFunc( aDivLog ){
// divLog - ссылка на DIV для вывода информации на экран;
var divLog;
// RetFunc - функция, которая вернётся как результат вызова функции, создающей
// объект и которая будет "логгировать" информацию.
var retFunc = function ( type, str ){
var curDate , span // переменные для временного хранения информации
// если второй параметр не передан, то первый параметр - информация для вывода в лог
if( typeof str === 'undefined' ){
str = type;
type = 0;
}
if( type != 3 ){
curDate = (new Date()).toLocaleString() + ': ';
}else{
curDate = '';
}
if( divLog ){
// выводим информацию на экран в DIV.
try{
span = document.createElement( 'SPAN' );
span.className = 'logSpan ';
switch( type ){
case 1: span.className += 'logWarn'; break;
case 2: span.className += 'logErr'; break;
case 3: span.className += 'logLog'; break;
default: span.className += 'logOk'
}
var needScroll = divLog.scrollTop + divLog.clientHeight >= divLog.scrollHeight - 30;
span.innerText = curDate + str;
divLog.appendChild( span );
divLog.appendChild( document.createElement( 'BR' ) );
if( needScroll )
span.scrollIntoView( false );
}catch( e ){
alert( 'Ошибка при добавлении информации в Лог.\n\n' + e.toStr( '\n' ) );
}
}
return str;
};
// функция Set - запоминание ссылки на DIV.
retFunc.set = function ( aDivLog ){
divLog = aDivLog;
};
if( typeof aDivLog !== 'undefined' ) retFunc.set( aDivLog );
return retFunc;
};
// --></SCRIPT>Если есть какие-то замечания или идеи – пишите. Особенно интересуют идеи – как сделать, чтобы на XP/2003 mshta.exe выгружалось из памяти, при закрытии приложения с работающим скриптом.
И ещё просьба – если есть возможность проверить работу обновления формы на Vista , Win 8 , Server 2008 , Server 2012, попробуйте запустить последний скрипт на этих ОС с обновлением в быстром режиме. Чтобы сделать это, надо в функции bodyOnLoad раскомментировать строку
// doEvents = DoEventsFunc( 100 , true );
и запустить HTA.

