{*******************************************************}
{                                                       }
{       Tocsg.WndUtil                                   }
{                                                       }
{       Copyright (C) 2022 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.WndUtil;

interface

uses
  System.SysUtils, Winapi.Windows, Winapi.Messages, Vcl.Forms, Tocsg.Thread,
  System.Generics.Collections;



type
  TEvActiveWndNotify = procedure(aSender: TObject; hActiveWnd: HWND) of object;
  TThdActiveWndMon = class(TTgThread)
  protected
    bSync_: Boolean;
    evNotify_: TEvActiveWndNotify;
    hActiveWnd_: HWND;
    procedure ProcessNotify;
    procedure Execute; override;
  public
    Constructor Create(bSync: Boolean = true);

    property OnActiveWndNotify: TEvActiveWndNotify write evNotify_;
  end;

const
  // GWL_STYLE
  WS_VAL: array [0..19] of DWORD = (
    WS_OVERLAPPED, WS_POPUP, WS_CHILD, WS_MINIMIZE, WS_VISIBLE, WS_DISABLED,
    WS_CLIPSIBLINGS, WS_CLIPCHILDREN, WS_MAXIMIZE, WS_CAPTION, WS_BORDER,
    WS_DLGFRAME, WS_VSCROLL, WS_HSCROLL, WS_SYSMENU, WS_THICKFRAME, WS_GROUP,
    WS_TABSTOP, WS_MINIMIZEBOX, WS_MAXIMIZEBOX
  );

  WS_STR: array [0..19] of String = (
    'WS_OVERLAPPED', 'WS_POPUP', 'WS_CHILD', 'WS_MINIMIZE', 'WS_VISIBLE', 'WS_DISABLED',
    'WS_CLIPSIBLINGS', 'WS_CLIPCHILDREN', 'WS_MAXIMIZE', 'WS_CAPTION', 'WS_BORDER',
    'WS_DLGFRAME', 'WS_VSCROLL', 'WS_HSCROLL', 'WS_SYSMENU', 'WS_THICKFRAME', 'WS_GROUP',
    'WS_TABSTOP', 'WS_MINIMIZEBOX', 'WS_MAXIMIZEBOX'
  );

  // GWL_EXSTYLE
  WS_EX_VAL: array [0..23] of LongInt = (
    WS_EX_DLGMODALFRAME, WS_EX_NOPARENTNOTIFY, WS_EX_TOPMOST, WS_EX_ACCEPTFILES,
    WS_EX_TRANSPARENT, WS_EX_MDICHILD, WS_EX_TOOLWINDOW, WS_EX_WINDOWEDGE,
    WS_EX_CLIENTEDGE, WS_EX_CONTEXTHELP, WS_EX_RIGHT, WS_EX_LEFT, WS_EX_RTLREADING,
    WS_EX_LTRREADING, WS_EX_LEFTSCROLLBAR, WS_EX_RIGHTSCROLLBAR, WS_EX_CONTROLPARENT,
    WS_EX_STATICEDGE, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOINHERITLAYOUT,
    WS_EX_LAYOUTRTL, WS_EX_COMPOSITED, WS_EX_NOACTIVATE
  );

  WS_EX_STR: array [0..23] of String = (
    'WS_EX_DLGMODALFRAME', 'WS_EX_NOPARENTNOTIFY', 'WS_EX_TOPMOST', 'WS_EX_ACCEPTFILES',
    'WS_EX_TRANSPARENT', 'WS_EX_MDICHILD', 'WS_EX_TOOLWINDOW', 'WS_EX_WINDOWEDGE',
    'WS_EX_CLIENTEDGE', 'WS_EX_CONTEXTHELP', 'WS_EX_RIGHT', 'WS_EX_LEFT', 'WS_EX_RTLREADING',
    'WS_EX_LTRREADING', 'WS_EX_LEFTSCROLLBAR', 'WS_EX_RIGHTSCROLLBAR', 'WS_EX_CONTROLPARENT',
    'WS_EX_STATICEDGE', 'WS_EX_APPWINDOW', 'WS_EX_LAYERED', 'WS_EX_NOINHERITLAYOUT',
    'WS_EX_LAYOUTRTL', 'WS_EX_COMPOSITED', 'WS_EX_NOACTIVATE'
  );

function GetWindowStyle(h: HWND): NativeInt;
function GetWindowStyleStr(h: HWND): String;

function GetWindowExStyle(h: HWND): NativeInt;
function GetWindowExStyleStr(h: HWND): String;

function GetWindowCaption(h: HWND): String;
function GetEditText(h: HWND): String;
procedure SetEditText(h: HWND; const sText: String);
function GetTopParentHWND(h: HWND): HWND;
function GetWndChildClass(h: HWND; const sClassName: String; hNextChild: HWND = 0): HWND;
function GetWndChildByCaption(h: HWND; const sFindCaption: String; hNextChild: HWND = 0): HWND;
function GetWndClassName(h: HWND): String;
function HasWndChild(hParent, hSrc: HWND): Boolean;

function GetProgressMax(h: HWND): Integer; // for "msctls_progress32" class
function GetProgressPos(h: HWND): Integer; // for "msctls_progress32" class

procedure SetScreenCenterForm(aForm: TForm);

function FindWindowFromProcessName(sPName: String; bIncInvisible: Boolean = false): HWND;

function SendData(h: HWND; dwCmd: DWORD; const sData: String): LONGLONG;

implementation

uses
  Tocsg.Trace, Tocsg.Exception, Tocsg.Process;

{ TThdActiveWndMon }

Constructor TThdActiveWndMon.Create(bSync: Boolean = true);
begin
  Inherited Create;
  bSync_ := bSync;
  hActiveWnd_ := 0;
  @evNotify_ := nil;
end;

procedure TThdActiveWndMon.ProcessNotify;
begin
  if Assigned(evNotify_) and (hActiveWnd_ <> 0) then
    evNotify_(Self, hActiveWnd_);
end;

procedure TThdActiveWndMon.Execute;
var
  h, hRecentWnd: HWND;
begin
  hRecentWnd := 0;
  while not Terminated and not GetWorkStop do
  begin
    h := GetForegroundWindow;
    if h <> hRecentWnd then
    begin
      hRecentWnd := h;
      if bSync_ then
      begin
        hActiveWnd_ := h;
        Synchronize(ProcessNotify);
      end else
      if Assigned(evNotify_) then
        evNotify_(Self, h);
    end;
    Sleep(500);
  end;
end;


{ Other }

// GWL_STYLE
function GetWindowStyle(h: HWND): NativeInt;
begin
  Result := 0;
  if h <> 0 then
    Result := GetWindowLong(h, GWL_STYLE);
end;

function GetWindowStyleStr(h: HWND): String;
var
  i: Integer;
  wStyle: LongInt;
begin
  Result := '';

  wStyle := GetWindowStyle(h);
  if wStyle <> 0 then
    for i := 0 to Length(WS_VAL) - 1 do
      if (WS_VAL[i] and wStyle) = WS_VAL[i] then
      begin
        if Length(Result) > 0 then
          Result := Result + '|' + WS_STR[i]
        else
          Result := WS_STR[i];
      end;
end;


// GWL_EXSTYLE
function GetWindowExStyle(h: HWND): NativeInt;
begin
  Result := 0;
  if h <> 0 then
    Result := GetWindowLong(h, GWL_EXSTYLE);
end;


function GetWindowExStyleStr(h: HWND): String;
var
  i: Integer;
  wExStyle: LongInt;
begin
  Result := '';

  wExStyle := GetWindowExStyle(h);
  if wExStyle <> 0 then
  begin
    for i := 0 to Length(WS_EX_VAL) - 1 do
    begin
      if (WS_EX_VAL[i] and wExStyle) = WS_EX_VAL[i] then
      begin
        if Length(Result) > 0 then
          Result := Result + '|' + WS_EX_STR[i]
        else
          Result := WS_EX_STR[i];
      end;
    end;
  end;
end;

function GetWindowCaption(h: HWND): String;
var
  nLen: Integer;
  str: array of Char;
begin

  Result := '';
  nLen := GetWindowTextLength(h);
  if nLen > 0 then
  begin
    Inc(nLen); // GetWindowText()에서 1 더해줘야 정상 처리 되는 경우 있음 24_1209 13:39:07 kku

    SetLength(str, nLen);
    ZeroMemory(@str[0], nLen * 2);
    if GetWindowText(h, @str[0], nLen) <> 0 then
      Result := String(PChar(@str[0]));
  end;
end;

function GetEditText(h: HWND): String;
var
  str: array of Char;
  dwResult: DWORD;
begin
  Result := '';

  SetLength(str, 1024);
  try
//    SendMessageTimeout(h, WM_GETTEXT, 1024, LPARAM(str), SMTO_NORMAL, 3000, @dwResult);
    SendMessage(h, WM_GETTEXT, 1024, LPARAM(str));
    Result := PChar(@str[0]);
  finally
    SetLength(str, 0);
  end;
end;

procedure SetEditText(h: HWND; const sText: String);
begin
  SendMessage(h, WM_SETTEXT, 0, LPARAM(@sText[1]));
end;

function GetTopParentHWND(h: HWND): HWND;
begin
  Result := h;
  while Result <> 0  do
  begin
    if GetParent(Result) = 0 then
      exit;
    Result:= GetParent(Result);
  end;
end;

function GetWndChildClass(h: HWND; const sClassName: String; hNextChild: HWND = 0): HWND;
var
  hChild: HWND;
  arrClassName: array [0..255] of Char;
begin
  Result := 0;
  hChild := GetWindow(h, GW_CHILD);

  if hChild <> 0 then
  begin
    if hNextChild <> 0 then
      hChild := GetWindow(hNextChild, GW_HWNDNEXT);

    while hChild <> 0 do
    begin
      if GetClassName(hChild, arrClassName, SizeOf(arrClassName)) > 0 then
      begin
        if CompareMem(@arrClassName[0], @sClassName[1], Length(sClassName)) then
        begin
          Result := hChild;
          exit;
        end;
      end;

      if GetWindow(hChild, GW_CHILD) <> 0 then
      begin
        Result := GetWndChildClass(hChild, sClassName);
        if Result <> 0 then
          exit;
      end;

      hChild := GetWindow(hChild, GW_HWNDNEXT);
    end;
  end;
end;

function GetWndChildByCaption(h: HWND; const sFindCaption: String; hNextChild: HWND = 0): HWND;
var
  hChild: HWND;
  sCaption: String;
begin
  Result := 0;
  hChild := GetWindow(h, GW_CHILD);

  if hChild <> 0 then
  begin
    if hNextChild <> 0 then
      while hNextChild <> hChild do
        hChild := GetWindow(hChild, GW_HWNDNEXT);

    while hChild <> 0 do
    begin
      sCaption := GetWindowCaption(hChild);
      if sCaption = sFindCaption then
      begin
        Result := hChild;
        exit;
      end;

      if GetWindow(hChild, GW_CHILD) <> 0 then
      begin
        Result := GetWndChildClass(hChild, sFindCaption);
        if Result <> 0 then
          exit;
      end;

      hChild := GetWindow(hChild, GW_HWNDNEXT);
    end;
  end;
end;

function GetWndClassName(h: HWND): String;
var
  arrClassName: array [0..255] of Char;
begin
  Result := '';
  if h <> 0 then
    if GetClassName(h, arrClassName, SizeOf(arrClassName)) > 0 then
      Result := String(arrClassName);
end;

function HasWndChild(hParent, hSrc: HWND): Boolean;
var
  hChild: HWND;
begin
  Result := false;
  hChild := GetWindow(hParent, GW_CHILD);

  while hChild <> 0 do
  begin
    if hChild = hSrc then
    begin
      Result := true;
      exit;
    end;

    if HasWndChild(hChild, hSrc) then
    begin
      Result := true;
      exit;
    end;

    hChild := GetWindow(hChild, GW_HWNDNEXT);
  end;
end;

function GetProgressMax(h: HWND): Integer; // for "msctls_progress32" class
const
  PBM_GETRANGE = $0407;
begin
  Result := SendMessage(h, PBM_GETRANGE, 0, 0);
end;

function GetProgressPos(h: HWND): Integer; // for "msctls_progress32" class
const
  PBM_GETPOS = $0408;
begin
  Result := SendMessage(h, PBM_GETPOS, 0, 0);
end;

procedure SetScreenCenterForm(aForm: TForm);
var
  i: Integer;
begin
  for i := 0 to Screen.MonitorCount - 1 do
    if Screen.Monitors[i].Primary then
    begin
      if (Screen.Monitors[i].Width > aForm.Width) and
         (Screen.Monitors[i].Height > aForm.Height) then
      begin
        aForm.Left := (Screen.Monitors[i].Width div 2) - (aForm.Width div 2);
        aForm.Top := (Screen.Monitors[i].Height div 2) - (aForm.Height div 2);
      end else begin
        aForm.Left := 0;
        aForm.Top := 0;
      end;
      break;
    end;
end;

function FindWindowFromProcessName(sPName: String; bIncInvisible: Boolean = false): HWND;
var
  h: HWND;
  llStyle: LONGLONG;
  sTemp: String;
begin
  Result := 0;
  try
    h := FindWindow(nil, nil);
    while h <> 0 do
    begin
      llStyle := GetWindowStyle(h);
      if bIncInvisible or
         ( ((llStyle and WS_VISIBLE) <> 0) and
           ((llStyle and WS_MINIMIZE) = 0) ) then
      begin
        sTemp := GetWindowCaption(h);
        if sTemp <> '' then
        begin
          sTemp := GetProcessNameFromWndHandle(h);
          if sTemp = sPName then
          begin
            Result := h;
            exit;
          end;
        end;
      end;
       h := GetWindow(h, GW_HWNDNEXT);
    end;
  except
    on E: ETgException do
      ETgException.TraceException(E, 'Fail .. FindWindowFromProcessName()');
  end;
end;

function SendData(h: HWND; dwCmd: DWORD; const sData: String): LONGLONG;
var
  CopyData: TCopyDataStruct;
begin
  CopyData.dwData := dwCmd;

  CopyData.cbData := (Length(sData) + 1) * 2;
  CopyData.lpData := PChar(sData);

  Result := SendMessage(h, WM_COPYDATA, 0, NativeInt(@CopyData));
  Application.ProcessMessages;
end;

end.
