喫茶XRAYトップページ


[7605] StringGridと格闘中…結果報告返信 削除
2014/5/17 (土) 23:25:58 taki
何とか使えそうです。

Gridの1列目に商品コード、2列目に商品名、3列目に数量とします。
商品コードを入力後、Tabキーを押すと商品名が検索されて表示され、
3列目へキャレットが移動するようにします。

このコードは色々と試行錯誤した結果です。参考になればうれしいです。

もし、別のコードを試される場合、プログラムが無限ループになることが多いので、
暴走しても終了できるようにIDEの環境で実行してください。


Form1にStringGrid1とTimer1を配置します。

(定義部は省略)

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  StringGrid1.Options := StringGrid1.Options + [goEditing,goTabs,goAlwaysShowEditor];
  StringGrid1.FixedCols := 0;
  StringGrid1.FixedRows := 0;
  Timer1.Enabled := false;
  Timer1.Interval := 100;   // もっと短くても良いと思います。
end;

procedure wait1000;  // 約1000m秒間待つ
var
  i:integer;
begin
  for i := 1 to 10 do begin
    Application.ProcessMessages;
    sleep(100);
  end;
end;

// データベースモドキ '123'を受け取ると'abc'を返し、それ以外は'Err'を返す。
function DBmodoki(val:string):string;
begin
  if val = '123' then result := 'abc' else result := 'Err';
end;

procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  beep;        // このイベントが発生したことを確認するため。本来不要
  wait1000;    // もし暴走した時に暴走を緩やかにするため。本来不要

  if ACol = 1 then begin
    Value := DBmodoki(StringGrid1.Cells[0,ARow]);
    Timer1.Enabled := true;       // TTimerにTabキーを押させる
  end;
end;

// ↓がミソです。
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := false;
  StringGrid1.Perform(WM_KEYDOWN, VK_TAB, 1);
  StringGrid1.Perform(WM_KEYUP, VK_TAB, 1);
end;

end.

============================================
以下の例は無限ループになります。

  if ACol = 1 then begin
    StringGrid1.Cells[1,ARow] := DBmodoki(StringGrid1.Cells[0,ARow]);
    Timer1.Enabled := true;  // TTimerにTabキーを押させる
  end;

キャレット(カーソル)があるセルに対してコードで文字を書き込むとループします。
StringGridからフォーカスを奪うShowmessage()等もループします。
もし、Showmessage()を使いたい場合はTimerにShowmessage()のコードを書きます。


[7606] Re:StringGridと格闘中…結果報告返信 削除
2014/5/18 (日) 21:05:57 Mr.XRAY
▼ takiさん

お疲れ様です.
時間のある時にいじらせていただきますです (^^;

>StringGridからフォーカスを奪うShowmessage()等もループします。

ですね.一般に,このようなイベント内では,Show... のような,
ダイアログを表示するのはタブーですね.


[7609] Re2:StringGridと格闘中…結果報告返信 削除
2014/5/19 (月) 10:45:23 taki
▼ Mr.XRAYさん

>
> お疲れ様です.
> 時間のある時にいじらせていただきますです (^^;


おかぽんさんのコードで解決ずみですが、せっかくなので私が試したことを書きます。

procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  beep;        // このイベントが発生したことを確認するため。本来不要
  wait1000;    // もし暴走した時に暴走を緩やかにするため。本来不要

  if ACol = 1 then begin
//------------------------------------------- キャレットが2列目に入ると
    テストするコード
//-------------------------------------------
  end;
end;

以下にテストしたコードです。

  StringGrid1.Cells[1,ARow] := 'aaaaa';     // 無限ループ
--
  Value := 'aaaaa';  // セーフ しかも直ちに表示される。正当な方法と思われる。
--
  ShowMessage('Help me!');     // 無限ループ
--
  Form2.ShowModal;  // エラー表示 無限ループ

無限ループに入ると、そのコードの次のコードに制御が行かない。
ErrSW:boolean;を定義しても

  StringGrid1.Cells[1,ARow] := 'aaaaa';     // 無限ループ
  ErrSW := true;    // ここへ制御が来ないのでErrSWはtrueにならない。

しかも、イベント発生(割り込み)→戻り先をスタックに積む→
イベント発生(割り込み)→戻り先をスタックに積む→…になり、
スタックがオーバーフローするみたいです。(未確認)

StringGridがEditモードでない場合は無限ループになりません。
--
  StringGrid1.Options := StringGrid1.Options - [goEditing,goAlwaysShowEditor];
  StringGrid1.Cells[1,ARow] := 'aaaaa';    // セーフ ただし確定後に表示
  StringGrid1.Options := StringGrid1.Options + [goEditing,goAlwaysShowEditor];
--
aaaaaは直ちに表示されず、キャレットが次のセルに移動した時に表示されます。
[goEditing,goAlwaysShowEditor]は[goEditing]だけでも大丈夫みたいです。

--
  StringGrid1.Options := StringGrid1.Options - [goEditing];
  StringGrid1.Cells[1,ARow] := 'aaaaa';
  StringGrid1.Col := 2;    // キャレットを3列目に移動
  StringGrid1.Options := StringGrid1.Options + [goEditing];
--
上記のコードは一見良いように見えますが、2列目に文字列があるとそのゴーストが
3列目に表示されます。
例えば、1列目に間違った商品コードを入れて、2列目に「商品未登録」となった時、
再び1列目に正しい商品コードを入れて、2列目に「きのこの山」と表示後、
3列目に「きのこの山」とゴーストが表示されます。(T_T)

--
  StringGrid1.Options := StringGrid1.Options - [goEditing];
  StringGrid1.Cells[1,ARow] := 'aaaaa';
  StringGrid1.Perform(WM_KEYDOWN, VK_TAB, 1);
  StringGrid1.Perform(WM_KEYUP, VK_TAB, 1);
  StringGrid1.Options := StringGrid1.Options + [goEditing];
--
これも上と同じで、幽霊が見えます。

みなさん。無駄なことをしないでくださいね。


[7613] Re3:StringGridと格闘中…結果報告返信 削除
2014/5/21 (水) 15:00:13 taki
続編です。
ErrSWを無限ループに入る前にTrueにすればいいことに気が…遅いわ!
それと、戻り先を積んだスタックが気になって実験してみました。

procedure TForm1.StringGrid1GetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  beep;        // このイベントが発生したことを確認するため。本来不要
  wait1000;    // もし暴走した時に暴走を緩やかにするため。本来不要

  Listbox1.Items.Append('1');
  if ErrSW then exit;
//
  Listbox1.Items.Append('2');
  if ACol = 1 then begin
//----------------------------------------
    ErrSW := true;         // 先にTrueにする
    Listbox1.Items.Append('3');
    StringGrid1.Cells[1,ARow] := 'aaaaa';     // 以前は無限ループした。
    Listbox1.Items.Append('4');
//---------------------------------------
  end;
  Listbox1.Items.Append('5');
  ErrSW := false;
end;

結果
1,2,5,1,2,5,1,2,3,1,4,5,1,2,5と記録されていました。

1,2,3でイベントが発生し、戻り先をスタックに積んで再び1に来てexitした後、
積んだ戻り先へ帰ってきて4,5と制御が流れたようです。

という事で、無限ループは回避できました。スタックメモリもオーバーフロー
しなくて済みそうです。
しか〜し、2列目(Col=1)には'aaaaa'は表示されませんでした。
不思議なことに、Button等をクリックして一度フォーカスを外すと'aaaaa'と
表示されます。

StringGrid1.Cells[1,ARow] := 'aaaaa';の代わりにShowmessage()でも大丈夫
でした。


[7614] Re4:StringGridと格闘中…結果報告返信 削除
2014/5/21 (水) 17:42:51 taki
度々すみません。これらのコードを試す人はいないとは思いますが念のため。

ErrSW:boolean;はグローバル変数として定義してください。
そしてFormCreate手続き内でfalseにセットしてください。



[7607] Re:StringGridと格闘中…結果報告返信 削除
2014/5/19 (月) 09:05:00 おかぽん
▼ takiさん

タイマーとかあまり使わないので、暴走した経験はありませんが
イカはどうでしょう?

procedure TForm1.StrGrid1SelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
  //列が0から1へ移動しようとしており、同一行であるかの判定
  if (StrGrid1.Col = 0) and (ACol = 1) and
     (StrGrid1.Row = AROw) then begin
    //商品コードから商品名を取得し入力
    StrGrid1.Cells[1, ARow] := DBmodoki(StrGrid1.Cells[0, ARow]);
    //ここではセル移動を行わない
    CanSelect := False;
    //数量列に移動
    StrGrid1.Col := 2;
  end;
end;

セルの移動前に、移動先のセルの値を書き換えるので、
> キャレット(カーソル)があるセルに対してコードで文字を書き込むとループします。
には、あてはまらないと思います。

Delphi 5 のコードですけど、参考になれば幸いです。

[7608] Re2:StringGridと格闘中…結果報告返信 削除
2014/5/19 (月) 10:37:16 taki
▼ おかぽんさん

ありがとうございます。

> セルの移動前に、移動先のセルの値を書き換えるので、
> > キャレット(カーソル)があるセルに対してコードで文字を書き込むとループします。
> には、あてはまらないと思います。
>
> Delphi 5 のコードですけど、参考になれば幸いです。


Delphi XE4 では

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
 //列が0から1へ移動しようとしており、同一行であるかの判定
  if (StringGrid1.Col = 0) and (ACol = 1) and
      (StringGrid1.Row = ARow) then begin
     //商品コードから商品名を取得し入力
    StringGrid1.Cells[1, ARow] := DBmodoki(StringGrid1.Cells[0, ARow]);
     //ここではセル移動を行わない
    CanSelect := False;
     //数量列に移動
    StringGrid1.Col := 2;
  end;
end;

で正常(かつ理想通り)に動くことを確認しました。

OnSelectCellイベントはグリッド内のセルが選択される前に発生します。
CanSelect パラメータを false に設定すると,セルを選択できなくすることができます。

地雷を踏む前に検知してフタをして飛び越えたらいいんですね。なるほど。



INCM/CMT
Cyclamen v3.82