AcadCapture.exe 2015/04/28 for Autocad / LT 2016

■概要
Autocad / LT 2016 で縮小画面をキャプチャします。
キャプチャした画像をダブルクリックすると、アクティブな図面が切り替わります。

・ビュワ~ンズームが設定してあると、キャプチャのタイミングが合いません。
 VTOPTIONSコマンドで、「画面移動とズームでアニメーションを使用」のチェックを外しておいて下さい。
・キャプチャ位置が合わないときは、AcadCapture.exe のプロパティー「互換性」で、高解像度DPI ... をチェックしてみて下さい。
・他の環境(PC)で、どの程度うまく動くのかは、不明です。タイミングが合わない可能性大です。
 プロパティパレットを表示させているだけでも、図面切り替えが遅くなり、タイミングが合わないことがあります。

※BricsCAD用、DraftSight用のキャプチャソフトを改造しているため、不要な設定項目が残っています。

■開発・動作確認環境
・Delphi XE5 Professional / Windows 8.1 64bit

・Autocad LT 2016 64bit (体験版)

■履歴
・2015/04/23
 初版作成
・2015/04/27
 初回起動時、読込エラーが連続で出るのを修正
 画面キャプチャ時、自フォームを最小化していたのを取りやめ
・2015/04/28
 シート番号、またはページ番号の属性取得を追加
 マウス右クリックのポップアップメニューに「再キャプチャ」を追加



■ダウンロード
 ダウンロードは中止しました。
 AcadCapture.zip(2015/04/28 exe本体のみ)

■ソースコード


unit AcadCaptureUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,{BricscadApp_TLB, BricscadDb_TLB,}
  Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Grids, ComObj, IniFiles, Vcl.Buttons,
  Vcl.Menus, ClipBrd ;

type
  TDwgDoc = record
    Index : integer;
    Name : string;
    Comment : string;
    Hnd : HWND;

  end;
type
  TForm2 = class(TForm)
    DrawGrid1: TDrawGrid;
    Panel1: TPanel;
    ComboBox1: TComboBox;
    SpeedButton1: TSpeedButton;
    PopupMenu1: TPopupMenu;
    N3: TMenuItem;
    N1: TMenuItem;
    N2: TMenuItem;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    N4: TMenuItem;
    N5: TMenuItem;
    procedure FormDestroy(Sender: TObject);
    procedure DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure FormResize(Sender: TObject);
    procedure DrawGrid1DblClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure N3Click(Sender: TObject);
    procedure N2Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
    procedure SpeedButton3Click(Sender: TObject);
    procedure DrawGrid1Click(Sender: TObject);
    procedure N5Click(Sender: TObject);
    procedure DrawGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private 宣言 }
  public
    { Public 宣言 }

    // 取得するブロック名と属性名
    BlkName, BlkName1, BlkName2, AttName, AttName1, AttName2 : string;
    AttFlag1, AttFlag2 : boolean;

    // 連続実行するLISP
    LspFileName : TFileName;
    LspCmdName  : string;
    LspCommand  : string;

    // マウス移動座標
    MoveX, MoveY: integer;
  end;

var
  Form2: TForm2;

  // キャプチャしたビットマップを保持
  BmpAry : array of TBitMap;
  // 図面名とその位置を保持
  DwgDocAry : array of TDwgDoc;
  // ビットマップの大きさ
  BmpW, BmpH : integer;
  // タイトル表示の高さ
  TitleH1, TitleH2 : integer;
  // シート名、ページ番号を表示する
  CmtDispFlag : boolean;
  // キャプチャ時、属性を取得
  CmtGetFlag  : boolean;
  BcadActiveFlag : boolean;

  // メインウィンドウ
  ACadMainWinHandle : HWND;
  // "テキストウィンドウ"
  ACadTextWinHandle : HWND;
  // テキストウィンドウのコマンド履歴ウィンドウのハンドル
  ACadTextHistHandle : HWND;
  // テキストウィンドウのコマンドラインウィンドウのハンドル
  ACadTextLineHandle : HWND;

  // MDIウィンドウ
  AcadMDIClientHandle:HWND;
  AcadMDIActiveHandle:HWND;
  AcadGraphicHandle : HWND;

  //プロセスID
  ACadProcessID : DWORD;

implementation


{$R *.dfm}

uses AcadCaptureCfgUnit;
//****************************************
// 最後の指定文字列より後を得る
//****************************************
function LastSubstringAfter(const ststr, s: string):String;
var
  st, stlen : integer;
  temp : string;
begin
  Result := '';
  if Pos(ststr, s) > 0 then begin
    temp := s;

    stlen := Length(ststr);
    while Pos(ststr, temp) > 0 do begin
      st := Pos(ststr, temp) + stlen;
      temp := Copy(temp, st, Length(temp) - st + 1);
      if temp = '' then break;
    end;
    Result := Trim(temp);
  end;
end;
//最初に現れたststrからedstrまでを返す
function StrAfterStrBefore(const ststr, edstr, s:string):String;
var
  st, ed:integer;
  temp : string;
begin
  Result:='';
  st:=Pos(ststr,s);
  if st > 0 then begin
    temp := s;
    Delete(temp,1,st + Length(ststr)-1);
    ed := Pos(edstr, temp)-1;
    if ed >= 0 then
      Result := Trim(Copy(temp, 1, ed));
  end;
end;
//****************************************
// 最後の指定文字列より後を得る
//****************************************
function LastStrAfter(const ststr, s: string):String;
var
  st, stlen : integer;
  temp : string;
begin
  Result := '';
  if Pos(ststr, s) > 0 then begin
    temp := s;

    stlen := Length(ststr);
    while Pos(ststr, temp) > 0 do begin
      st := Pos(ststr, temp) + stlen;
      temp := Copy(temp, st, Length(temp) - st + 1);
      if temp = '' then break;
    end;
    Result := Trim(temp);
  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;

//****************************************
// 画面の指定位置を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 EnumWindowProcMainWin(h: HWND; lp: LParam): BOOL; stdcall;
var
  Title : array [0..255] of char;
  ClassName : array [0..255] of char;
begin
  result := true;
  // タイトルを得る
  if GetWindowText(h, Title, 255) <> 0 then begin
    if (StrLComp(Title, 'DWG TrueView ', 13) = 0) or
       (StrLComp(Title, 'AutoCAD ', 8 )= 0) or
       (StrLComp(Title, 'Autodesk ', 9 )= 0) then begin
      GetClassName(h, ClassName, 255);
      if ((StrLComp(ClassName, 'Afx:', 4) = 0) and (StrPos(ClassName, ':8:') <> nil)) or
        (StrLComp(ClassName, 'AfxMDIFrame', 11) = 0) then begin
        ACadMainWinHandle := h;
        GetWindowThreadProcessId(h, @ACadProcessID);
        result := false;
      end;
    end;
  end;
end;
//****************************************
// メインウィンドウのハンドルを得る
//****************************************
function GetACadMainWinHandle : HWND;
begin
  EnumWindows(@EnumWindowProcMainWin, 0);
  Result := ACadMainWinHandle;

end;
//****************************************
// トップレベルのテキストウィンドウ
//****************************************
function EnumWindowProcTextWin(h: HWND; lp: LParam): BOOL; stdcall;
var
  Title : array [0..255] of char;
  ClassName : array [0..255] of char;
  PID : DWORD;
begin
  result := true;
  if GetWindowText(h, Title, 255) <> 0 then begin
    if (AnsiStrLComp(Title, 'AutoCAD ', 8) = 0 ) or
       (AnsiStrLComp(Title, 'DWG TrueView ', 13) = 0 ) or
       (AnsiStrLComp(Title, 'Autodesk ', 9) = 0) then begin
      GetClassName(h, ClassName, 255);
      if (StrLComp(ClassName, 'Afx:', 4) = 0) and (StrPos(ClassName, ':b:') <> nil) then begin
        if AnsiStrPos(Title, 'テキスト ウィンドウ') <> nil then begin
          GetWindowThreadProcessId(h, @PID);
          if PID = ACadProcessID then begin
            ACadTextWinHandle := h;
            result := false;
          end;
        end;
      end;
    end;
  end;
end;

//****************************************
// トップレベルのテキストウィンドウ
//****************************************
function GetAcadTextWinHandle:HWND;
var
  h : HWND;
begin
  // プロセスIDを取得するため
  GetAcadMainWinHandle;

  //トップレベルにあるテキストウィンドウのハンドルを取得
  EnumWindows(@EnumWindowProcTextWin, 0);
  Result := ACadTextWinHandle;
  h := GetWindow(ACadTextWinHandle, GW_CHILD);
  h := GetWindow(h, GW_CHILD);
  // コマンドライン
  ACadTextLineHandle := GetWindow(h, GW_CHILD);
  // コマンド履歴
  ACadTextHistHandle := GetWindow(ACadTextLineHandle, GW_HWNDNEXT);
end;

//****************************************
// 描画ウィンドウ
//****************************************
function EnumCWinProc_Graph(h: HWND; lparam: Integer):Bool;stdcall;
var
  ClassName : array [0..255] of char;
begin
  Result := True;

  GetClassName(h, ClassName, 255);
  if (Pos('Afx:', ClassName) = 1) and (Pos(':28:', ClassName) > 1) then begin
    AcadGraphicHandle := h;
    Result := False;
  end;
end;

//****************************************
// MDIClient ウィンドウ
//****************************************
function EnumCWinProc_MDIClient(h:HWND;lparam:Integer):Bool;stdcall;
var
  ClassName : array [0..255] of char;
begin
  Result := True;
  GetClassName(h, ClassName, 255);
  if ClassName = 'MDIClient' then begin
    AcadMDIClientHandle := h;
    Result := False;
  end;
end;
//****************************************
// MDIClient ウィンドウ
//****************************************
function GetAcadMDIClientHandle:HWND;
begin
  GetAcadMainWinHandle;
  EnumChildWindows(AcadMainWinHandle, @EnumCwinProc_MDIClient, 0);
  result := AcadMDIClientHandle;
end;

//****************************************
// MDIActive ウィンドウ
//****************************************
function GetAcadMDIActiveHandle:HWND;
begin
  result := 0;
  GetAcadMDIClientHandle;
  if IsWindow(AcadMDIClientHandle) then begin
    //アクティブなウィンドウハンドル
    //どちらでもOK
    //AcadMDIActiveHandle:= GetWindow(AcadMDIClientHandle,GW_CHILD);
    AcadMDIActiveHandle := SendMessage(AcadMDIClientHandle, WM_MDIGETACTIVE, 0, 0);
    result := AcadMDIActiveHandle;
  end;
end;

//****************************************
//アクティブなグラフィック画面のハンドル
//****************************************
function GetAcadGraphicHandle:HWND;
begin
  GetAcadMainWinHandle;
  GetAcadMDIActiveHandle;
  EnumChildWindows(AcadMDIActiveHandle, @EnumCwinProc_Graph,0);
  result := AcadGraphicHandle;
end;
//****************************************
// テキストウィンドウのコマンドラインの文字列を得る
//****************************************
function GetACadTextLine:string;
begin
  GetACadTextWinHandle;
  result := GetWindowString(ACadTextLineHandle);
end;

//****************************************
// テキストウィンドウのコマンド履歴の文字列を得る
//****************************************
function GetACadTextHist:string;
begin
  GetACadTextWinHandle;
  result := GetWindowString(ACadTextHistHandle);
end;

//******************************************
// AutoCAD(LT)2007以上に文字列を送信
//******************************************
function SendACadCommand(const cmd: String):boolean;
var
   wmes: array[0..511] of WideChar;
   cs: TCopyDataStruct;
   len: integer;
begin
  result := false;
  GetACadMainWinHandle;
  if IsWindow(ACadMainWinHandle) then begin
    len := Length(cmd) + 1;

    //String から UNICODE 文字列に変換
    StringToWideChar(cmd, wmes, len);

    cs.dwData:= 1;//必ず1
    // 2バイトずつ
    cs.cbData:= len * 2;
    cs.lpData:= @wmes;

    SendMessage(ACadMainWinHandle, WM_COPYDATA, 0, LPARAM(@cs));
    PostMessage(ACadMainWinHandle, WM_NULL, 0, 0);

    result := true;
  end;
end;

//****************************************
// システム変数を取得 (2016)
//****************************************
function GetACadVariable(const syscmd: string): string;
var
  st, ed: integer;
  s : string;
begin
  result := '';
  s := '';
  if IsWindow(ACadMainWinHandle) then begin
    if (UpperCase(syscmd) = 'ACADVER') or (UpperCase(syscmd) = 'LAYERPMODE') then
      SendACadCommand(syscmd + #13)
    else
      SendACadCommand('''SETVAR ' + syscmd + #13);

    // 2015/04/22 追加
    // 実際のコマンドラインではなく、隠れているコマンドラインから文字列を
    // 取得するため、若干のタイムラグが必要みたい
    Sleep(100);
    // トップレベルのテキストウィンドウのコマンドラインの文字列を取得
    s := GetACadTextLine;    //2015.4.22 変更

    // 区切り文字の位置を取得
    st := LastDelimiter('<', s);
    ed := LastDelimiter('>', s);
    if (st > 0) and (st < ed) then
      result := Trim(Copy(s, st + 1, ed - st - 1));

    if result <> '' then
      // キャンセルを発行
      SendACadCommand(#3)
    else begin
      SendACadCommand(#3);
      s := GetACadTextHist;
      // 2015/04/22 追加
      Sleep(100);

      s := LastStrAfter(syscmd, s);
      st := LastDelimiter('=', s);
      ed := LastDelimiter('(', s);
      if (st > 0) and (st < ed) then
        result := Trim(Copy(s, st + 1, ed - st - 1));
    end;
    {
    // " を削除する
    if Copy(result,1,1) = '"' then begin
      s := Copy(result,2,Length(result)-1);
      if Pos('"', s) > 0 then s := Copy(s,1,Pos('"', s));
      result := '"' + s;
    end;
    }
  end;
end;

// *******************************
// ActoCAD をアクティブに
// *******************************
function SetAcadActive: boolean;
begin
  Result := False;
  GetAcadMainWinHandle;
  if IsWindow(AcadMainWinHandle) then begin
    SetForegroundWindow(AcadMainWinHandle);
    Result := True;
  end;
end;

// *******************************
// DwgDocAryをソート
// *******************************
procedure SortDwgDocAry;
var
  len, n, m : integer;
  i, j: integer;
  DwgDocTmp : TDwgDoc;
  s, s1 : string;
begin
  n := Length(DwgDocAry);
  if n > 1 then begin

    // コメント文字列の最大文字数
    len := 0;
    for i := 0 to n -1 do begin
      m := DwgDocAry[i].Comment.Length;
      if len < m then len := m;
    end;
    // 文字列比較のため同じ長さにする
    for i := 0 to n -1 do begin
      with DwgDocAry[i] do begin
        m := Comment.Length;
        if m < len then begin
          for j := 1 to len - m do begin
            Comment:= ' ' + Comment;
          end;
        end;
      end;
    end;
    // ソート
    for i := 0 to n - 2 do begin
      s := DwgDocAry[i].Comment + ':' + DwgDocAry[i].Name;
      for j := i + 1 to n - 1 do begin
        s1 := DwgDocAry[j].Comment + ':' + DwgDocAry[j].Name;
        if s1 < s then begin
          DwgDocTmp := DwgDocAry[i];
          DwgDocAry[i] := DwgDocAry[j];
          DwgDocAry[j] := DwgDocTmp;
          s := s1;
        end;
      end;
    end;
  end;
end;

procedure TForm2.ComboBox1Change(Sender: TObject);
var
  idx : integer;
  i : integer;
begin
  with ComboBox1 do begin
    idx := ItemIndex;
    if idx >= 0 then
      i := Integer(Items.Objects[idx])
    else
      i := -1;
  end;
  if (i >= 0) and (i < Length(DwgDocAry)) then begin
    with DrawGrid1 do begin
      Col := i mod ColCount;
      Row := i div ColCount;
    end;
  end;
end;

procedure TForm2.DrawGrid1Click(Sender: TObject);
var
  i : integer;
begin
  with DrawGrid1 do
    i := Row * ColCount + col;
  ComboBox1.ItemIndex := i;
end;

// **************************
// キャプチャ画像をダブルクリックで図面切り替え
// **************************
procedure TForm2.DrawGrid1DblClick(Sender: TObject);
var
  i : integer;
begin

  with DrawGrid1 do begin
    i := Row * ColCount + col;
    if (i >= 0) and (i < Length(DwgDocAry)) then begin

      if GetACadMainWinHandle = 0 then Exit;

      // MDI のウィンドウを探す
      GetAcadMDIClientHandle;

      // 図面を切り替え
      SendMessage(AcadMDIClientHandle, WM_MDIACTIVATE, DwgDocAry[i].Hnd, 0);

      if BcadActiveFlag then
        // AutoCADをアクティブに
        SetForegroundWindow(AcadMainWinHandle)
      else
        // 自フォームをアクティブに
        SetForegroundWindow(Handle);

      ComboBox1.ItemIndex := i;
    end;
  end;
end;

// **************************
// キャプチャ画像を描画
// **************************
procedure TForm2.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  i, w, h, dx, dy : integer;
  idx : integer;
begin
  with DrawGrid1 do begin
    // 現在の表示位置
    i := ARow * ColCount + Acol;

    idx := -1;
    if (i >= 0) and (i < Length(DwgDocAry)) then
      idx := DwgDocAry[i].Index;

    if (idx >= 0) and (idx < Length(BmpAry)) then begin
      w := BmpAry[idx].Width;
      h := BmpAry[idx].Height;

      dx := (DefaultColWidth - w) div 2;

      if not CmtDispFlag then
        dy := (DefaultRowHeight - h - TitleH1) div 2
      else
        dy := (DefaultRowHeight - h - TitleH2) div 2;

      with Canvas do begin
        if not CmtDispFlag then
          Draw(Rect.Left + dx, Rect.Top + dy + TitleH1, BmpAry[idx])
        else
          Draw(Rect.Left+dx,Rect.Top + dy + TitleH2, BmpAry[idx]);

        // 背景を塗りつぶし
        if (ARow = Row) and (ACol = Col) then
          Brush.Color := clBlue
        else
          Brush.Color := clGray;

        if not CmtDispFlag then
          Rectangle(Rect.Left + 1, Rect.Top + 1, Rect.Right - 1, Rect.Top + TitleH1 + 1)
        else
          Rectangle(Rect.Left + 1, Rect.Top + 1, Rect.Right - 1, Rect.Top + TitleH2 + 1);

        Font.Color := clWHITE;

        // タイトルを描画
        TextOut(Rect.Left + 3, Rect.Top + 3, ExtractFileName(DwgDocAry[i].Name));

        // コメントを描画
        if CmtDispFlag then
          TextOut(Rect.Left + 3, Rect.Top + 3 + TitleH1 + 1, DwgDocAry[i].Comment);

        // 標準の設定に戻す
        Pen.Style := psSolid;
        Brush.Style := bsClear;
        Pen.Color := clGray;
        Pen.Width := 1;
        Rectangle(Rect);
      end;
    end
    else begin
      with Canvas do begin
        Brush.Color := clWhite;
        FillRect(Rect);
      end;
    end;
  end;
end;

procedure TForm2.DrawGrid1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  MoveX := X;
  MoveY := Y;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  ini : TIniFile;
begin

  // 2015/04/27 追加
  SetLength(BmpAry, 0);
  SetLength(DwgDocAry, 0);

  BmpH := 168;
  BmpW := 240;
  TitleH1 := 20;
  TitleH2 := 42;

  with DrawGrid1 do begin
    DefaultColWidth := BmpW;
    DefaultRowHeight := BmpH + 20;
    Options := Options + [goThumbTracking];
  end;

  BlkName := 'TITLE';
  AttName := 'ZSHEET';

  BlkName1 := BlkName;
  AttName1 := AttName;

  BlkName2 := BlkName;
  AttName2 := 'ZITEM9';

  AttFlag1 := True;
  AttFlag2 := False;

  // ダブルクリックで Bricscad をアクティブに
  BcadActiveFlag := True;

  // コメント表示
  CmtDispFlag := False;

  // キャプチャ時、コメントを取得
  CmtGetFlag := True;

  LspFileName := '';
  LspCmdName := '';
  LspCommand := '';

  ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
  try
    with Ini do begin
      Top := ReadInteger('Form', 'Top', Top);
      Left := ReadInteger('Form', 'Left', Left);
      Width:= ReadInteger('Form', 'Width', Width);
      Height := ReadInteger('Form', 'Height', Height);
      if Left >=  Screen.Width then Left := (Screen.Width + Width) div 2;
      if Top >= Screen.Height then Top := (Screen.Height + Height) div 2;

      BlkName1 := ReadString('Dcad', 'BlkName1', BlkName1);
      BlkName2 := ReadString('Dcad', 'BlkName2', BlkName2);
      AttName1 := ReadString('Dcad', 'AttName1', AttName1);
      AttName2 := ReadString('Dcad', 'AttName2', AttName2);
      AttFlag1 := ReadBool('Dcad', 'AttFlag1', AttFlag1);
      AttFlag2 := ReadBool('Dcad', 'AttFlag2', AttFlag2);

      BcadActiveFlag := ReadBool('Dcad', 'ActiveFlag', BcadActiveFlag);

      CmtDispFlag := ReadBool('DcadCapt', 'CmtDispFlag', CmtDispFlag);
      CmtGetFlag := ReadBool('DcadCapt', 'CmtGetFlag', CmtGetFlag);

      LspFileName := ReadString('Dcad', 'LispFileName', LspFileName);
      LspCmdName := ReadString('Dcad', 'LispCmdName', LspCmdName);
      LspCommand := ReadString('Dcad', 'LispCommand', LspCommand);

      // メニュー
      N2.Caption := LspCmdName;
      SpeedButton2.Caption := LspCmdName;
    end;
  finally
    ini.Free;
  end;
end;

procedure TForm2.FormDestroy(Sender: TObject);
var
  i : integer;
  ini : TIniFile;
begin
  // Bitmap を破棄
  if Length(BmpAry) > 0 then begin
    for i := 0 to Length(BmpAry) -1 do
      BmpAry[i].Free;
  end;

  ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
  try
    with Ini do begin
      WriteInteger('Form', 'Top', Top);
      WriteInteger('Form', 'Left', Left);
      WriteInteger('Form', 'Width', Width);
      WriteInteger('Form', 'Height', Height);

      WriteString('Dcad', 'BlkName1', BlkName1);
      WriteString('Dcad', 'BlkName2', BlkName2);
      WriteString('Dcad', 'AttName1', AttName1);
      WriteString('Dcad', 'AttName2', AttName2);
      WriteBool('Dcad', 'AttFlag1', AttFlag1);
      WriteBool('Dcad', 'AttFlag2', AttFlag2);

      WriteBool('Dcad', 'ActiveFlag', BcadActiveFlag);

      WriteBool('DcadCapt', 'CmtDispFlag', CmtDispFlag);
      WriteBool('DcadCapt', 'CmtGetFlag', CmtGetFlag);

      WriteString('Dcad', 'LispFileName', LspFileName);
      WriteString('Dcad', 'LispCmdName', LspCmdName);
      WriteString('Dcad', 'LispCommand', LspCommand);
    end;
  finally
    ini.Free;
  end;
end;

// ***************************
// フォームリサイズ
// ***************************
procedure TForm2.FormResize(Sender: TObject);
var
  n, xcnt, ycnt:Integer;
begin
  n := Length(BmpAry);
  if n > 0 then begin
    with DrawGrid1 do begin
      xcnt := (Width - 23) div DefaultColWidth;
      if xcnt=0 then xcnt:=1;
      ycnt := n div xcnt;
      if n mod xcnt > 0 then Inc(ycnt);
      ColCount := xcnt;
      RowCount := ycnt;
    end;
  end;
end;

procedure TForm2.N2Click(Sender: TObject);
begin
end;

// ************************************
// 設定フォーム
// ************************************
procedure TForm2.N3Click(Sender: TObject);
begin
  Form3 := TForm3.Create(Form2);
  with Form3 do begin
    try
      Edit1.Text := BlkName1;
      Edit2.Text := AttName1;
      Edit3.Text := BlkName2;
      Edit4.Text := AttName2;
      Edit5.Text := BmpW.ToString;
      Edit6.Text := BmpH.ToString;
      Edit7.Text := TitleH1.ToString;
      Edit8.Text := TitleH2.ToString;

      CheckBox1.Checked := AttFlag1;
      CheckBox2.Checked := AttFlag2;
      CheckBox4.Checked := BcadActiveFlag;

      CheckBox3.Checked := CmtGetFlag;
      CheckBox5.Checked := CmtDispFlag;

      Edit9.Text := LspFileName;
      Edit10.Text := LspCommand;
      Edit11.Text := LspCmdName;


      if ShowModal = mrOk then begin
        BlkName1 := Edit1.Text;
        AttName1 := Edit2.Text;
        BlkName2 := Edit3.Text;
        AttName2 := Edit4.Text;

        BmpW := StrToIntDef(Edit5.Text, BmpW);
        BmpH := StrToIntDef(Edit6.Text, BmpH);
        TitleH1 := StrToIntDef(Edit7.Text, TitleH1);
        TitleH2 := StrToIntDef(Edit8.Text, TitleH2);

        AttFlag1 := CheckBox1.Checked;
        if AttFlag1 and (BlkName1 <> '') and (AttName1 <> '') then begin
          BlkName := BlkName1;
          AttName := AttName1;
        end;
        AttFlag2 := CheckBox2.Checked;
        if AttFlag2 and (BlkName2 <> '') and (AttName2 <> '') then begin
          BlkName := BlkName2;
          AttName := AttName2;
        end;

        BcadActiveFlag := CheckBox4.Checked;

        CmtGetFlag := CheckBox3.Checked;
        CmtDispFlag := CheckBox5.Checked;

        LspFileName := Edit9.Text;
        LspCommand := Edit10.Text;
        LspCmdName := Edit11.Text;

        // メニュー
        N2.Caption := LspCmdName;
        SpeedButton2.Caption := LspCmdName;

      end;
    finally
      Free;
    end;
  end;
end;

// ************************************
// 再キャプチャ
// ************************************
procedure TForm2.N5Click(Sender: TObject);
var
  i,idx : integer;
  ACol, ARow :integer;
  s, s1 : string;
  n : integer;
  scrW, scrH : Integer;
  ARect : TREct;
  ALeft, ATop : integer;
  scale : Double;
  APoint : TPoint;
begin
  with DrawGrid1 do begin

    // マウス座標をCol,Rowに
    MouseToCell(MoveX, MoveY, ACol, ARow);
    Row := ARow;
    Col := ACol;
    i := Row * ColCount + Col;
    if (i >= 0) and (i < Length(DwgDocAry)) then begin
      idx := DwgDocAry[i].Index;

      if GetACadMainWinHandle = 0 then Exit;

      // MDI のウィンドウを探す
      GetAcadMDIClientHandle;
      // 図面を切り替え
      SendMessage(AcadMDIClientHandle, WM_MDIACTIVATE, DwgDocAry[i].Hnd, 0);
      SetForegroundWindow(AcadMainWinHandle);
      ComboBox1.ItemIndex := i;

      // MDI のウィンドウを探す
      GetAcadMDIClientHandle;
      // 描画ウィンドウを探す
      GetAcadGraphicHandle;
      // ウィンドウの位置と大きさを取得
      GetWindowRect(AcadGraphicHandle, ARect);
      // グラフィックウィンドウの左上座標
      ALeft := ARect.Left;
      ATop  := ARect.Top;

      // Autocad からグラフィック画面のサイズを取得
      s := GetAcadVariable('SCREENSIZE');
      n := Pos(',', s);
      scrW := Trunc(StrToFloatDef(Copy(s, 1, n -1), 0));
      scrH := Trunc(StrToFloatDef(Copy(s, n + 1  ), 0));

      // システム変数取得失敗であれば、終了
      if (scrW = 0) or (scrH = 0) then Exit;

      // 高解像度DPI対策
      ALeft := Trunc(ALeft * scrW / (ARect.Right  - ARect.Left));
      ATop  := Trunc(ATop  * scrH / (ARect.Bottom - ARect.Top));

      // キャプチャするビットマップの大きさに縮小する尺度
      if BmpH / BmpW < scrH / scrW then
        scale := BmpW / scrW
      else
        scale := BmpH / scrH;
      //
      SetCursorPos(ALeft + 1, ATop + 1);

      // 図面範囲でズーム
      s1 := GetAcadVariable('LIMMIN');
      s  := GetAcadVariable('LIMMAX');

      if (s <> '') and (s1 <> '') then
        SendAcadCommand(#3 + 'ZOOM '+ s + #13 + s1 + #13)
      else
        SendAcadCommand(#3 + 'ZOOM ALL' + #13);

      SendAcadCommand(#3 + 'ZOOM ' + Format('%.3f', [scale]) + 'X' + #13);

      // 描画待ち
      Sleep(500);

      if Length(BmpAry) > idx then begin
        // キャプチャ
        CaptureToBmp(
          ALeft + (scrW - BmpW) div 2,
          ATop  + (scrH - BmpH) div 2,
          BmpAry[idx].Width, BmpAry[idx].Height, BmpAry[idx]);
      end;
      // 直前の画面表示に戻す
      SendAcadCommand(#3 + 'ZOOM PRE' + #13);
      Repaint;
    end;
  end;

  // マウスカーソルを戻す
  APoint.X := MoveX;
  APoint.Y := MoveY;
  APoint := DrawGrid1.ClientToScreen(APoint);
  SetCursorPos(APoint.X, APoint.Y);


  SetForegroundWindow(Handle);
end;

// ************************************
// DXFファイルを作成し、属性を1個取得
// ************************************
function ReadDxfAtt(const InsertName: string; const AttRibName: string):string;
var
  sl : TStringList;
  fname : TFileName;
  i : integer;
  insFlag, attFlag, hasAtt : boolean;
  s : string;
  AblkName , AattName, AattString : string;
  cd, idx : integer;
begin
  Result := '';

  if (InsertName = '') or (AttRibName = '')  then Exit;

  fname := ChangeFileExt(ParamStr(0), '.dxf');
  DeleteFile(fname);

  if SetAcadActive then begin
    SendAcadCommand(#3 + 'FILEDIA 0'+#13);
    SendAcadCommand('DXFOUT' + #13 + fname + #13 + 'V 2004 16' + #13);
    SendAcadCommand('''FILEDIA 1' + #13);

    // ファイル作成待ち
    Sleep(500);

    if FileExists(fname) then begin
      sl := TStringList.Create;
      try
        sl.LoadFromFile(fname);
        if sl.Count > 1 then begin

          insFlag := False;
          hasAtt  := False;
          attFlag := False;

          // ENTITIESセクションを探す
          idx := sl.IndexOf('ENTITIES');

          for i := idx div 2 + 1 to sl.Count div 2 - 1 do begin
            // DXFコード
            cd := StrToInt(sl[i * 2]);
            // その値
            s := sl[i * 2 + 1];

            if insFlag then begin

              // 属性取得終了
              if hasAtt and (cd = 0) and (s = 'SEQEND') then begin
                insFlag := False;
                hasAtt  := False;
                attFlag := false;
              end;

              if hasAtt and (AblkName = '') and (cd = 2) then
                AblkName := s;

              if attFlag then begin
                // 属性の値を保持
                if cd = 1 then
                  AattString := s;
                // 属性名
                if cd = 2 then begin
                  AattName := s;
                  // ブロック名、属性名が同じ
                  if (InsertName = ABlkName) and (AttRibName = AattName) then begin
                    Result := AattString;
                    Break;
                  end;
                end;
              end;
              if hasAtt and (cd = 0) and (s = 'ATTRIB') then
                attFlag := True;
            end;

            if (cd = 0) and (s = 'INSERT') then begin
              insFlag := True;
              hasAtt  := False;
              attFlag := False;
              AblkName := '';
            end;
            // 属性有
            if (cd = 66) and (Trim(s) = '1') then
              hasAtt := True;
            // ENTITIES セクション終わり
            if (cd = 0) and (s = 'ENDSEC') then
              Break;
          end;
        end;
      finally
        sl.Free;
      end;
    end;
  end;
end;

// ***********************************
// 画面キャプチャ
// ***********************************
// あらかじめ、ビュワ~ンズームを止めておくこと
// VTOPTIONS コマンド「推移を表示」
// 「画面移動とズームでアニメーションを使用」のチェックを外す
procedure TForm2.SpeedButton1Click(Sender: TObject);
var
  scrH, scrW : integer;
  ARect : TRect;
  ALeft, ATop{, AWidth, AHeight} : integer;
  i : integer;
  scale : double;
  h : HWND;
  dwgTitle : string;
  cnt : integer;
  s, s1 : string;
  n :integer;
  horg : HWND;
begin

  // すでにBitmap が作成されているときは、破棄
  if Length(BmpAry) > 0 then begin
    for i := 0 to Length(BmpAry) -1 do
      BmpAry[i].Free;
  end;

  // メインウィンドウのハンドルを取得
  if GetACadMainWinHandle = 0 then Exit;

  // 最小化されていれば戻す
  if isIconic(AcadMainWinHandle) then
    OpenIcon(AcadMainWinHandle);

  SetForegroundWindow(AcadMainWinHandle);

  // MDI のウィンドウを探す
  GetAcadMDIClientHandle;

  // 描画ウィンドウを探す
  GetAcadGraphicHandle;

  // ウィンドウの位置と大きさを取得
  GetWindowRect(AcadGraphicHandle, ARect);

  // グラフィックウィンドウの左上座標
  ALeft := ARect.Left;
  ATop  := ARect.Top;

  // 適当な大きさで動的配列を確保
  SetLength(DwgDocAry, 100);
  SetLength(BmpAry,    100);

  // 図面数を取得
  cnt := 0;

  // MDIClient下の最初のウィンドウ(ActiveWindow)
  h := GetWindow(AcadMDIClientHandle, GW_CHILD);
  while h <> 0 do begin
    // タイトルを取得
    dwgTitle := GetWindowCaption(h);
    s := Uppercase(dwgTitle);
    // 「スタート」「Drawing1.dwg」は無視
    if (Pos('.DWG', s) > 1) and (Pos('DRAWING', s) = 0) then begin
      with DwgDocAry[cnt] do begin
        Name    := dwgTitle;
        Index   := cnt;
        Comment := '';
        Hnd     := h;
      end;
      Inc(cnt);
    end;
    // 次のウィンドウへ
    h := GetWindow(h, GW_HWNDNEXT);
  end;

  SetLength(DwgDocAry, cnt);
  SetLength(BmpAry,    cnt);

  // Autocad からグラフィック画面のサイズを取得
  s := GetAcadVariable('SCREENSIZE');
  n := Pos(',', s);
  scrW := Trunc(StrToFloatDef(Copy(s, 1, n -1), 0));
  scrH := Trunc(StrToFloatDef(Copy(s, n + 1  ), 0));

  // システム変数取得失敗であれば、終了
  if (scrW = 0) or (scrH = 0) then Exit;

  // 高解像度DPI対策
  ALeft := Trunc(ALeft * scrW / (ARect.Right  - ARect.Left));
  ATop  := Trunc(ATop  * scrH / (ARect.Bottom - ARect.Top));

  // キャプチャするビットマップの大きさに縮小する尺度
  if BmpH / BmpW < scrH / scrW then
    scale := BmpW / scrW
  else
    scale := BmpH / scrH;

  // 2015/04/27 取りやめ
  // 自フォームを最小に
  //WindowState := wsMinimized;
  //Sleep(100);

  SetCursorPos(ALeft + 1, ATop + 1);

  try

    // アクティブなドキュメントを取得
    h := SendMessage(AcadMDIClientHandle, WM_MDIGETACTIVE, 0, 0);
    horg := h;

    for i := 0 to Length(DwgDocAry) -1 do begin

      SendMessage(AcadMDIClientHandle, WM_MDIACTIVATE, DwgDocAry[i].Hnd, 0);

      // ウィンドウ切り替え待ち
      Sleep(600);

      // 図面範囲でズーム
      s1 := GetAcadVariable('LIMMIN');
      s  := GetAcadVariable('LIMMAX');
      if (s <> '') and (s1 <> '') then
        SendAcadCommand(#3 + 'ZOOM '+ s + #13 + s1 + #13)
      else
        SendAcadCommand(#3 + 'ZOOM ALL' + #13);

      SendAcadCommand(#3 + 'ZOOM ' + Format('%.3f', [scale]) + 'X' + #13);

      // 描画待ち
      Sleep(500);

      // キャプチャ用ビットマップを作成
      BmpAry[i] := TBitmap.Create;
      BmpAry[i].PixelFormat := pf32bit;

      // ビットマップの大きさ
      BmpAry[i].Width  := BmpW;
      BmpAry[i].Height := BmpH;

      // キャプチャ
      CaptureToBmp(
        ALeft + (scrW - BmpW) div 2,
        ATop  + (scrH - BmpH) div 2,
        BmpAry[i].Width, BmpAry[i].Height, BmpAry[i]);

      // 直前の画面表示に戻す
      SendAcadCommand(#3 + 'ZOOM PRE' + #13);

      // 描画待ち
      Sleep(300);

      if CmtGetFlag then begin
        s := ReadDxfAtt(BlkName, AttName);
        DwgDocAry[i].Comment := s;
      end;


    end;
    SendMessage(AcadMDIClientHandle, WM_MDIACTIVATE, horg, 0);

    // シート番号、ページ番号順にソート
    SortDwgDocAry;

    with ComboBox1 do begin
      Items.Clear;
      Sorted := False;
      for i := 0 to Length(DwgDocAry) - 1 do begin
        Items.AddObject(DwgDocAry[i].Comment +':'+ DwgDocAry[i].Name , TObject(i));
        if horg = DwgDocAry[i].Hnd then ItemIndex := i;

      end;
    end;
    FormResize(self);
    ComboBox1Change(self);
  except
    ;
  end;

  // 2015/04/27 取りやめ
  // 自フォームを戻す
  //WindowState := wsNormal;
  // 自フォームをアクティブに
  SetForegroundWindow(Handle);
end;

procedure TForm2.SpeedButton2Click(Sender: TObject);
begin
  // コマンド実行
  //N2Click(self);
end;

procedure TForm2.SpeedButton3Click(Sender: TObject);
begin
  // 設定
  N3Click(self);
end;

end.