MX Component を使わない PLC I/O チェッカーたたき台 (2019/03/28)
GX Works2 のデバイス一括モニタの画面を表示させたままで使います。
姑息な方法ながら、何とか実用になりそうです。いずれ FX (8進数アドレス)にも対応予定です。
あと Bluetooth 端末からのコマンド受信、応答を追加すればスマホから I/O チェックができるようになります。

unit GXW2Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Buttons;
type
TBitAry = array [0..159] of Boolean;
type
TForm4 = class(TForm)
Edit1: TEdit;
Image1: TImage;
Button3: TButton;
Timer1: TTimer;
Edit2: TEdit;
Edit3: TEdit;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
Edit4: TEdit;
Button4: TButton;
Label1: TLabel;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
Button1: TButton;
Edit5: TEdit;
SpeedButton5: TSpeedButton;
SpeedButton6: TSpeedButton;
SpeedButton7: TSpeedButton;
SpeedButton8: TSpeedButton;
SpeedButton9: TSpeedButton;
SpeedButton10: TSpeedButton;
procedure Button3Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
procedure SpeedButton5Click(Sender: TObject);
procedure SpeedButton7Click(Sender: TObject);
procedure SpeedButton9Click(Sender: TObject);
procedure SpeedButton10Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form4: TForm4;
// SpreadSheet ウィンドウ
SpreadSheetHwnd : HWND;
// SpreadSheet ウィンドウの左上座標
shtCaptLeft, shtCaptTop : integer;
// 座標補正の係数
shtXScale, shtYScale : double;
// 列幅、行高
shtColWidth, shtRowHeight : double;
pickXOff, pickYOff : integer;
DeviceEditHwnd : HWND;
DeviceComboHwnd : HWND;
// メインウィンドウ
GXW2FrameHwnd : HWND;
BitAryNew : TBitAry;
BitAryOld : TBitAry;
devHeadOld : string;
passFlag : boolean;
implementation
{$R *.dfm}
//****************************************
// 画面の指定位置をBitmapに変換
//****************************************
procedure CaptureToBmp(Lf, Tp, W, H: Integer; bmp: TBitmap);
const
CAPTUREBLT = $40000000;
var
hdcScreen : HDC;
begin
bmp.Width := W;
bmp.Height := H;
hdcScreen := CreateDC('DISPLAY', nil, nil, nil);
try
BitBlt( bmp.Canvas.Handle, 0, 0, W, H, hdcScreen, Lf, Tp, SRCCOPY or CAPTUREBLT);
finally
DeleteDC(hdcScreen);
end;
end;
//******************************************
// ウィンドウのタイトル(キャプション)を得る
//******************************************
function GetWindowCaption(h : HWND) : string;
var
Title : array [0..255] of char;
begin
result := '';
if GetWindowText(h, Title, 255) <> 0 then
result := Title;
end;
//******************************************
// 他のプロセス内のコントロールの文字列を得る
//******************************************
function GetWindowString(h : HWND) : string;
var
p : PChar;
len : LongInt;
begin
result := '';
//ウィンドウの文字列のバイト数を取得
//終端のNULL文字を含まない文字列の長さ(バイト数)
len := SendMessage(h, WM_GETTEXTLENGTH, 0, 0);
if len > 0 then begin
//終端のNULL文字を含むサイズを確保
GetMem(p, (len + 1) * 2);
//格納するバッファの最大サイズ(終端のNULL文字を含む長さ)
//文字列バッファ
SendMessage(h, WM_GETTEXT, (len+1)*2, LongInt(p));
//文字列がバッファサイズより長いとき、後部がカットされる
result := string(p);
FreeMem(p);
end;
end;
function GetHwndClassName(h : HWND):string;
var
PC : PChar;
Len : Integer;
Classname : string;
begin
ClassName := '';
if not IsWindow(h) then exit;
GetMem(PC, 100);
try
Len := GetClassName(h, PC, 100);
SetString(Classname, PC, Len);
finally
FreeMem(PC);
end;
result := Classname;
end;
function EnumCWinProc_DeviceCombo(h: HWND; lparam: Integer): Bool; stdcall;
var
Title : array [0..255] of char;
begin
result := true;
if GetWindowText(h, Title, 255) <> 0 then begin
if Pos('デバイス/バッファメモリ一括モニタ', Title) = 1 then begin
DeviceComboHwnd := h;
Result := False;
end;
end;
end;
function EnumCWinProc_SpreadSheet(h: HWND; lparam: Integer): Bool; stdcall;
var
ClassName : string;
begin
result := true;
ClassName := GetHwndClassName(h);
if 'SPR32AU70_SpreadSheet' = ClassName then begin
SpreadSheetHwnd := h;
Result := False;
end;
end;
// Window に文字列を送る
function SendCharHwnd(h: HWND; const s: string):boolean;
var
i : integer;
begin
result := False;
if h <> 0 then begin
for i := 1 to Length(s) do
SendMessage(h, WM_CHAR, Word(s[i]), 0);
result := true;
end;
end;
// Window に文字列を送る
function SendTextHwnd(h: HWND; const s : string):boolean;
begin
result := False;
if h <> 0 then begin
SendMessage(h, WM_SETTEXT, 0, LPARAM(PChar(s)));
result := true;
end;
end;
procedure TForm4.Button1Click(Sender: TObject);
// 先頭デバイス変更
var
x, y : integer;
pt, pt0 : TPoint;
begin
if IsWindowVisible(DeviceEditHwnd) then begin
GetCursorPos(pt0);
SendTextHwnd(DeviceEditHwnd, Trim(Edit5.Text));
SetForegroundWindow(DeviceEditHwnd);
// [Enter]
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
// セル 0,0 に移動
x := shtCaptLeft + Trunc(15 * shtColWidth + pickXOff * shtXScale);
y := shtCaptTop + Trunc(shtRowHeight + pickYOff * shtYScale);
SetCursorPos(x, y);
pt.X := x; pt.Y := y;
// マウス直下の Window を取得
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// マウスクリックで Active に
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
end;
SetCursorPos(pt0.X, pt0.Y);
end;
end;
procedure TForm4.Button3Click(Sender: TObject);
var
h : HWND;
i, j, n : integer;
ARect : TRect;
bmp : TBitmap;
Pnt : PByteArray;
R, G, B : Byte;
x, y : integer;
pt : TPoint;
begin
// メインウィンドウを探す
GXW2FrameHwnd := FindWindow('GXW2FrameWnd', nil);
if IsWindowVisible(GXW2FrameHwnd) and not IsIconic(GXW2FrameHwnd) then begin
//キャプション「デバイス/バッファメモリ一括モニタ」を探す
EnumChildWindows(GXW2FrameHwnd, @EnumCwinProc_DeviceCombo, 0);
h := GetWindow(DeviceComboHwnd, GW_CHILD);
h := GetWindow(h, GW_CHILD);
for i := 0 to 2 do
h := GetWindow(h, GW_HWNDNEXT);
DeviceEditHwnd := GetWindow(h, GW_CHILD);
// デバイス先頭
Edit1.Text := GetWindowString(DeviceEditHwnd);
if Edit4.Text = '' then Edit4.Text := Edit1.Text;
if Edit5.Text = '' then Edit5.Text := Edit1.Text;
// 先頭デバイスが変わったときは、1回パス
if devHeadOld <> Edit1.Text then begin
passFlag := True;
devHeadOld := Edit1.Text;
// 反転デバイスを変更
Edit4.Text := Edit1.Text;
end
else
passFlag := False;
// クラス名「SPR32AU70_SpreadSheet」 を探す
EnumChildWindows(GXW2FrameHwnd, @EnumCwinProc_SpreadSheet, 0);
GetWindowRect(SpreadSheetHwnd, ARect);
// 左上座標を保持
shtCaptLeft := Trunc(ARect.Left + 243 * shtXScale);
shtCaptTop := ARect.Top;
shtColWidth := 504 * shtXScale / 16 ; // 16 列
shtRowHeight := 371 * shtYScale / 11 ; // 11 行 (タイトルを含む行数)
// アクティブにする
pt.X := shtCaptLeft; pt.Y := shtCaptTop;
if WindowFromPoint(pt) <> SpreadSheetHwnd then begin
SetForegroundWindow(SpreadSheetHwnd);
Sleep(100);
end;
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// キャプチャ
bmp := TBitmap.Create;
try
bmp.PixelFormat:= TPixelFormat.pf24bit; // 重要
CaptureToBmp(shtCaptLeft, shtCaptTop, Trunc(shtColWidth*16)+1, Trunc(shtRowHeight * 11)+1, bmp);
Image1.Picture.Assign(bmp);
finally
bmp.Free;
end;
// ON/OFF をセルの色でチェック
for i := 0 to 9 do begin
y:= Trunc(shtRowHeight * (i+1) + pickYOff * shtYScale);
Pnt := Image1.Picture.Bitmap.ScanLine[y];
for j := 0 to 15 do begin
x := Trunc(shtColWidth * j + pickXOff * shtXScale);
R := Pnt[x * 3 + 2];
G := Pnt[x * 3 + 1];
B := Pnt[x * 3];
// Blue は ON
BitAryNew[i * 16 + 15 - j] := (R = 0) and (G = 0) and (B = $FF);
end;
end;
if not passFlag then begin
// 結果表示
n := StrToIntDef('$'+ Copy(Edit1.Text, 2), 0);
for i:= 0 to 159 do begin
if BitAryNew[i] <> BitAryOld[i] then begin
Edit2.Text := Copy(Edit1.Text, 1, 1) + IntToHex(n + (i div 16) * 16 + i mod 16, 3);
if BitAryNew[i] then Edit3.Text := 'ON'
else Edit3.Text := 'OFF';
end;
end;
end
else begin
Edit2.Text := '';
Edit3.Text := '';
for i := 0 to 159 do
BitAryNew[i] := False;
end;
BitAryOld := BitAryNew;
end;
end;
end;
procedure TForm4.Button4Click(Sender: TObject);
// デバイス ON/OFF 反転
var
h, k, n, m : integer;
x, y : integer;
pt, pt0 : TPoint;
begin
GetCursorPos(pt0);
// 先頭デバイス
h := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
// 反転対象のデバイス
k := StrToIntDef('$' + Copy(Edit4.Text, 2), -1);
if k >= 0 then begin
k := k - h;
n := k div 16;
m := k mod 16;
x := shtCaptLeft + Trunc((15 - m) * shtColWidth + pickXOff * shtXScale);
y := shtCaptTop + Trunc((n + 1) * shtRowHeight + pickYOff * shtYScale);
SetCursorPos(x, y);
pt.X := x; pt.Y := y;
// マウス直下の Window を取得
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// マウスクリックで Active に
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
// [Shift] + [Enter] キーで反転
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
end;
end;
// マウス位置を戻す
SetCursorPos(pt0.X, pt0.Y);
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
shtXScale := 1.0;
shtYScale := 1.0;
// デバイス SpreadSheet のセルをアクティブにする左上からオフセット
// ピクセルの色取得の位置
pickXOff := 10;
pickYOff := 8;
end;
procedure TForm4.SpeedButton10Click(Sender: TObject);
// デバイス変更
begin
Edit5.Text := 'Y' + Copy(Edit5.Text, 2);
end;
procedure TForm4.SpeedButton1Click(Sender: TObject);
// [+10],[+1]
var
n, m : integer;
begin
n := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
m := StrToIntDef('$' + Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton1 then
Inc(m)
else
m := m + $10;
if m - n < 160 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToHex(m, 1);
end;
procedure TForm4.SpeedButton2Click(Sender: TObject);
// [-10],[-1]
var
n, m : integer;
begin
n := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
m := StrToIntDef('$' + Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton2 then
Dec(m)
else
m := m - $10;
if m - n >= 0 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToHex(m, 1);
end;
procedure TForm4.SpeedButton5Click(Sender: TObject);
// 先頭アドレス変更 [+80],[+100]
var
m : integer;
begin
m := StrToIntDef('$' + Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton5 then
m := m+ $80
else
m := m + $100;
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToHex(m, 1);
end;
procedure TForm4.SpeedButton7Click(Sender: TObject);
// 先頭アドレス変更 [-80],[-100]
var
m : integer;
begin
m := StrToIntDef('$' + Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton7 then
m := m - $80
else
m := m - $100;
if m >= 0 then
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToHex(m, 1);
end;
procedure TForm4.SpeedButton9Click(Sender: TObject);
// デバイス変更
begin
Edit5.Text := 'X' + Copy(Edit5.Text, 2);
end;
procedure TForm4.Timer1Timer(Sender: TObject);
begin
Button3Click(self);
end;
end.