Помощь - Поиск - Пользователи - Календарь
Полная версия: Узнать Handle окна по имени файла
Форум «Всё о Паскале» > Современный Паскаль и другие языки > Делфи
Unconnected
Привет всем.

Мне нужно узнать хэндл окна. Допустим, запускаю я cmd через ShellExecute, и нужно мне получить её хэндл. Через FindWindow нельзя, т.к. заголовок cmd может быть разный - у некоторых в начале приписывается "Администратор" (видимо, сидят под администратором). Я думаю, можно перебирать все окна и искать по слову cmd в заголовке. Попробовал ф-ю EnumWindows, вот так:

var mypointer:pointer;
t:hwnd;
function MyCallbackFunction(Wnd:HWnd; P: Pointer):Bool; stdcall;
begin
messagebox(0,pchar(GetModuleName(GetClassLong(wnd,GCL_HMODULE))),'',0);
if pos('CMD',uppercase(GetModuleName(GetClassLong(wnd,GCL_HMODULE))))>0 then t:=wnd;
end;

...

shellexecute(0,'open','cmd',0,0,sw_show);
EnumWindows(@MyCallbackFunction, LongInt(MyPointer));
if t=0 then messagebox(0,'dfdf','',0);
...

, и нифига, getmodulename не хочет возвращать имя файла по хэндлу. Помогите, а то меня скоро в гугле как ддос-бота забанят))
volvo
Кто тебе сказал, что имя модуля - это заголовок? Заголовок получается через GetWindowText...

var
t: HWND = 0;

function MyCallbackFunction(myWnd: HWND; lp: LPARAM): Bool; stdcall;
var
szCaption: array[0 .. MAXCHAR - 1] of Char;
begin
GetWindowText(myWnd, szCaption, MAXCHAR);
if pos('CMD', UpperCase(szCaption)) > 0 then t := myWnd;
result := True;
end;

procedure TForm1.Button11Click(Sender: TObject);
begin
ShellExecute(0, 'open', 'cmd', nil, nil, SW_SHOW);

// Подожди какое-то время, чтоб окно успело отобразиться
Sleep(1000);

EnumWindows(@MyCallbackFunction, LPARAM(0));
if t = 0 then MessageBox(0, 'Не нашел', '', MB_OK);
end;

Unconnected
Спасибо, это работает. Ещё вопрос, как можно сменить раскладку в чужом окне? Дело в том, что если эмулировать нажатия клавиатуры, то в другом окне они будут отображаться в соответствии с тамошней раскладкой, т.е. передаю ord('F') - появляется 'а'. На одном форуме я нашёл такую функцию:


function ChangeLayout(
const RemoteHandle: THandle): Boolean;
var
Dumme: DWORD;
Layout: HKL;
begin
Layout := LoadKeyboardLayout('00000409', KLF_ACTIVATE);
Result := SendMessageTimeOut(RemoteHandle, WM_INPUTLANGCHANGEREQUEST,
0, Layout, SMTO_ABORTIFHUNG, 200, Dumme) <> 0;
if Result then Result := SendMessageTimeOut(RemoteHandle, WM_INPUTLANGCHANGEREQUEST,
DEFAULT_CHARSET, Layout, SMTO_ABORTIFHUNG, 200, Dumme) <> 0;
end;
(передаю хэндл нужного окна)

, но она была под изменение на русскую раскладку, а мне нужна английская. Там я в SetKeyboardLayout изменил код раскладки на английский и в предпоследней строке вместо RUSSIAN_CHARSET вписал DEFAULT_CHARSET (в модуле windows.pas константы поглядел), только вот не работает. Кажется, там не DEFAULT_CHARSET, но вот по логике вещей ENGLISH_CHARSET тоже нет.. Кое-где пишут, что вообще хук нужен.
volvo
Цитата
только вот не работает
В чем неработоспособность проявляется? Я вот сделал так:
procedure TForm1.Button12Click(Sender: TObject);
var h: HWND;
begin
h := FindWindow(nil, 'Untitled - Notepad');
if h <> 0 then
begin
if ChangeLayout(h) then ShowMessage('True')
else ShowMessage('False');
end
else
ShowMessage('Notepad was not found');
end;

, запустил Блокнот, поменял в нем раскладку на русскую, а потом нажал эту кнопку... раскладка блокнота тут же поменялась на английскую. Значит, работает?

То, что Rouse_ выкладывает, всегда работает... smile.gif
Unconnected
раскладка блокнота тут же поменялась на английскую. 


Это с оригинальной функцией

  function ChangeRemoteWndKeyboardLayoutToRussian(
const RemoteHandle: THandle): Boolean;
var
Dumme: DWORD;
Layout: HKL;
begin
Layout := LoadKeyboardLayout('00000419', KLF_ACTIVATE);
Result := SendMessageTimeOut(RemoteHandle, WM_INPUTLANGCHANGEREQUEST,
0, Layout, SMTO_ABORTIFHUNG, 200, Dumme) <> 0;
if Result then
Result := SendMessageTimeOut(RemoteHandle, WM_INPUTLANGCHANGEREQUEST,
RUSSIAN_CHARSET, Layout, SMTO_ABORTIFHUNG, 200, Dumme) <> 0;
end;
?

Если да, то вообще из названия я понял, что она исключительно на русскую меняет раскладку. У меня первый язык русский, я делал так:


changelayout(t);
sleep(1000); //выждал на всякий случай
simulatekeystroke(ord('F'),0);


, в t - хэндл cmd. Передаётся буква а, русская и маленькая. По умолчанию у меня во вновь открытых приложениях русский язык. Так вот, ни с оригинальной, ни с моей правленной функциями раскладка не поменялась (я под Seven, если что).
volvo
А... Ну, так ты бы и говорил, что хочешь работать с той самой консолью, которую искал в первом посте... Отсюда и проблемы. С консолью - только через хук WH_SHELL (консоли - это вообще темные лошадки). Ну, или (если консоль активная, в фокусе ввода, в смысле) - то пошли ей комбинацию клавиш, переключающих раскладку.

Но это будет частное решение, на одной машине, чтоб переключиться Ru -> En понадобится одна посылка, на другой - может понадобиться больше, зависит от порядка и количества доступных раскладок... Да и получить раскладку для чужого консольного окна не так просто, так что отсылать Alt+Shift и проверять, не стала ли она английской - не получится... А если другая комбинация клавиш выбрана для переключения раскладки? Вопросом много, ответов - нет...
Unconnected
Ого, ну и заморочки. Я сейчас попробовал работать с ней через буфер обмена, т.е. вставлять туда строку, а в cmd эмулировать Ctrl+V - так нет же, даже вручную ни Ctrl-V, ни Shift-Ins не работают. Спасибо за инфу, почитаю про WH_Shell. smile.gif
volvo
У тебя будет гораздо больше проблем, чем ты думаешь сейчас, если ты еще и с хуками заморочишься. Начиная с того, что на WinNT хуки не работают с консольными приложениями ( это является небезопасным, вот что говорит MS по этому поводу: http://support.microsoft.com/kb/108232 ).

Теперь - вопрос на засыпку: а зачем тебе что-то печатать в консоли, да еще созданной через ShellExecute? Если тебе просто нужна консоль, скажем, для вывода, то есть AllocConsole + GetStdHandle, и пиши сколько хочешь в STD_OUTPUT_HANDLE... О том, как запустить процесс и перехватить его вывод написано в DRKB. Так зачем консоль понадобилась?

Кстати, еще одно: есть более интересный способ получить хендл консоли, чем приведенный тобой: создавать процесс через CreateProcess, а потом делать вот так
Unconnected
Цитата
Теперь - вопрос на засыпку: а зачем тебе что-то печатать в консоли, да еще созданной через ShellExecute? Если тебе просто нужна консоль, скажем, для вывода, то есть AllocConsole + GetStdHandle, и пиши сколько хочешь в STD_OUTPUT_HANDLE... О том, как запустить процесс и перехватить его вывод написано в DRKB. Так зачем консоль понадобилась?


Ну, мне надо исполнить cmd-команду, но как будто от имени пользователя, через bat-файл не вариант. Короче, я отказался от cmd в пользу окошка Выполнить. Делаю так:

var t,editt,but:hwnd;
...
keybd_event(vk_LWIN,0,0,0);
keybd_event(byte('R'),0,0,0);
keybd_event(byte('R'),0,KEYEVENTF_KEYUP,0);
keybd_event(vk_LWIN,0,KEYEVENTF_KEYUP,0);
t:=findwindow(nil,'Выполнить');
editt:=findwindowex(t,0,'ComboBox','');
but:=findwindowex(t,0,'Button','OK');
...


Окошко находится, ComboBox в нём - тоже, даже получается записать туда строку, а вот кнопка что-то не хочет. Вообще, FindWindowEx ищет на всех дочерних "компонентах" окнА, чей хэндл был передан, или только на основной форме? Просто на Win7 кнопки на какой-то белой полосе, типа TBevel или TPanel, что-то такое. Можно, конечно, сделать EnumChildWindows, чтобы посмотреть все контролы, но может и так можно?

Цитата
создавать процесс через CreateProcess, а потом делать вот так

Ага, мне говорили про такой способ, но я испугался множества входных параметров этой функции smile.gif
volvo
blink.gif Эт чего было? Ты что, серьезно вызываешь окно Run именно так, имитируя всю последовательность действий? Я бы так не делал, есть специализированные методы, которые поддерживаются и будут поддерживаться всеми версиями Windows, независимо от того, как изменится интерфейс. Я про Shell...

Да, и еще:
  1. Насколько я знаю, диалог Run сразу после появления является активным, чтобы можно было не теряя времени начать набирать команду. Этим можно воспользоваться.
  2. Ты когда-нибудь сам-то нажимаешь на "Ok", когда ввел команду? Я - нет. Просто жму Enter, там же DEFPUSHBUTTON, окно само отреагирует...

Исходя из вышесказанного, я бы сделал так:

var
s: string = 'regedit';

procedure TForm1.Button1Click(Sender: TObject);
var
ShellApplication: Variant;
pgui: TGUIThreadinfo;

foreWin: HWND;
begin
ShellApplication := CreateOleObject('Shell.Application');
try

ShellApplication.FileRun; // Будет вызвано именно то окошко, как и при Start -> Run
Sleep(500);

foreWin := GetForegroundWindow();
if foreWin = 0 then ShowMessage('Try FindWindowEx')
else begin
pgui.cbSize := SizeOf(TGUIThreadinfo);
GetGUIThreadInfo(GetWindowThreadProcessId(foreWin), pgui);
SendMessage(pgui.hwndFocus, WM_SETTEXT, Length(s), Integer(@s[1]));
PostMessage(pgui.hwndFocus, WM_KEYDOWN, VK_RETURN, Integer(True));
end;

finally
ShellApplication := Unassigned;
end;
end;


Только разберись, как оно работает, не копируй код в проект без этого, ибо: Читать здесь smile.gif
Unconnected
Разобрался, узнал много нового ) Только вот этот код у меня, в консольном приложении (видимо, вся соль именно в этом), не отрабатывал и выбивал ошибку, мол, не вызвано CoInitialize. Я добавил строчку CoInitialize(nil) в самом начале процедуры и CoUninintialize в конце, работать стало, но всё равно выдавало AV в конце программы, пришлось убрать CoUninitialize (это, наверное, и неправильно, но и мне ошибки не нужны)).
Кстати, там при передаче сообщения Эдиту использовалась SendMessage, а при передаче кнопке - Post. Это типа чтобы текст эдиту присвоился, информация об этом вернулась и только потом нажалась бы кнопка?

//проникся блогом, оказалось, я истинный CodeMonkey, прямо по всем признакам lol.gif
volvo
Цитата
Я добавил строчку CoInitialize(nil) в самом начале процедуры и CoUninintialize в конце
Как ни пытался - не получилось у меня, чтоб AV вылетел. Может ты несколько раз вызываешь эту процедуру? Тогда пара Co/CoUn должна быть в начале/конце программы, а не подпрограммы.

Просто убирать CoUninitialize нельзя:
Цитата
To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize


Цитата
Это типа чтобы текст эдиту присвоился, информация об этом вернулась и только потом нажалась бы кнопка?
Это привычка, отсылать текст через SetMessage, чтобы не дать повода для ошибки. Если здесь сделать PostMessage... Хотя, чего я-то? У Paul DiLascia в блоге все написано, читай перевод
Unconnected
Ага, поставил именно в начале и конце программы - помогло, я твой код просто в процедуру оборачивал. А вот

Цитата
ShellApplication := CreateOleObject('Shell.Application');


, это ж мы в переменной плавающего типа создаём объект, да? А где можно почитать про методы и свойства класса OleObject (конструктор которого был вызван именно с таким параметром)? Я искал по msdn.com, но там чего-то всё относительно Visual Studio Tools for Office.
volvo
Ты про Shell.Application? Вот тут: MSDN -> Shell Object
Unconnected
Спасибо, в итоге добился, чего хотел smile.gif
Unconnected
Volvo, а вот, чисто теоретически, можешь предположить, почему твой код из 10-го поста может не сработать? Вариант со слабой машиной и как следствие маленькой задержкой отметается. Просто человек один говорит, что у него просто открывается Выполнить, там висит команда и ничего не происходит, типа, кнопка не нажалась. Или же вообще, что кнопка Ок неактивна, и становится активна только после нажатия кнопки какой-нибудь в поле ввода. У меня и на основной и на виртуальной машинах всё работает.. Пользуюсь этим, убрал проверку:


Procedure writerun(n:string);
begin
ShellApplication := CreateOleObject('Shell.Application');
try
ShellApplication.FileRun;
Sleep(500);
foreWin := GetForegroundWindow();
pgui.cbSize := SizeOf(TGUIThreadinfo);
GetGUIThreadInfo(GetWindowThreadProcessId(foreWin), pgui);
SendMessage(pgui.hwndFocus, WM_SETTEXT, Length(n), Integer(@n[1]));
// postMessage(pgui.hwndFocus, WM_KEYDOWN, VK_SPACE, Integer(True)); //здесь я пробовал дополнительно //ввести пробел, чтобы кнопка активировалась
postMessage(pgui.hwndFocus, WM_KEYDOWN, VK_RETURN, Integer(True));
finally
ShellApplication := Unassigned;
end;
end;


//added: может, stdcall надо было написать возле заголовка процедуры? Там же апи, как-никак..
volvo
Теоретически - могу, такое будет, когда программа запускается из-под IDE. У меня у самого так было, пока не запустил из-под Эксплорера... Причем GUI-приложение отрабатывает прекрасно и из-под IDE тоже, а вот консольное - не хочет. Все-таки, консоль - это необычное окно... Зря ты с ней затеялся. Другой причины - не вижу пока.

Цитата
может, stdcall надо было написать возле заголовка процедуры?
В данном случае это ни на что не влияет. Модель вызова важна в Callback-функциях. У тебя никакого КоллБэка нет. На всякий случай, присоедини сюда или в Личку проект полностью, может у тебя там еще чего заморочено?

Добавлено через 2 мин.
Цитата
//здесь я пробовал дополнительно //ввести пробел, чтобы кнопка активировалась
Не поможет, я тоже пробовал, когда запускал из-под IDE. Пока окно не потеряет фокус, и потом его снова не получит - ничего не помогало. Но снимать с Run-диалога фокус, а потом снова активизировать его - это по-моему уже лишнее...
Unconnected
Цитата
а вот консольное - не хочет. Все-таки, консоль - это необычное окно...


Странно, ведь апи-команды, вызываемые из консоли, вроде как ничем не отличаются от тех, которые вызываются из GUI-приложения.

У меня, кстати, из консольного всё заработало, с тем добавлением пробела. Странно это.
volvo
Цитата
ведь апи-команды, вызываемые из консоли, вроде как ничем не отличаются от тех, которые вызываются из GUI-приложения.
Зато поведение консольного окна отличается от того, как ведет себя окно графическое. Возьмет и переключится на консоль в тот момент, когда ты собираешься занести строку в Run-диалог. На долю секунды, не важно, насколько. Но в момент посылки строки Run-диалог будет неактивен. И все... Кнопка "Ok" останется за-disable-нной, несмотря на то, что потом фокус опять перешел назад на диалог.

Учитывай, что хозяином консоли является системный процесс csrss. Что так ему в голову придет - ты знаешь? Я - нет...

Вопрос на засыпку: а что ты, собственно, выиграл от того, что сделал консольную программу? Думаешь, размер ее будет меньше, чем у оконного приложения? Вряд ли, напиши на чистом API (если тебе от программы больше ничего не надо, кроме как запускать что-то через через Start -> Run, то в 50 строк точно уложишься) - размер будет еще меньше, чем размер консольного приложения... Зато работать будет стабильно...
Unconnected
Даа, апи сила) Почитал, переделал (коряво, кажется, но работает)). Размер, правда, остался примерно таким же - вместе с модулями variants,comobj,activex (которые нужны для того кода) и нужным мне Sysutils. Зато - стабильно. Ведь действительно, ну не мог я объяснить, почему у меня 10 раз кряду всё отрабатывает, а у другого - ровно через 3 раза))

Спасибо за советы smile.gif
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.