5

VCL フォーム アプリケーションで 2 つの TDBGrid コンポーネントのスクロールを同期させようとしています。スタックの問題がなければ、各グリッド コンポーネントの WndProc をインターセプトするのが困難です。スクロール イベントで WM_VSCROLL メッセージを送信しようとしましたが、それでも正しく動作しません。スクロールバーをクリックしたり、セルを強調表示したり、マウスの上下ボタンで機能する必要があります。全体のアイデアは、2 つのグリッドを隣り合わせにして、一種の一致するダイアログを表示することです。

試した

SendMessage( gridX.Handle, WM_VSCROLL, SB_LINEDOWN, 0 );

また

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
Msg.Result := CallWindowProc( POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );

   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
end;

procedure TForm1.GridxCustomWndProc( var Msg: TMessage );
begin
   if ( Msg.Msg = WM_VSCROLL ) then 
   begin
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
   end;
   inherited WndProc( Msg );
end;

1 つ目は一時的な解決策にすぎず、2 つ目は無効なメモリ読み取りが発生し、3 つ目はスタック オーバーフローが発生します。したがって、これらの解決策はどれもうまくいかないようです。このタスクを達成する方法についての情報が欲しいです! 前もって感謝します。

更新: 解決策

  private
    [...]
    GridXWndProc, GridXSaveWndProc: Pointer;
    GridYWndProc, GridYSaveWndProc: Pointer;
    procedure GridXCustomWndProc( var Msg: TMessage );
    procedure GridYCustomWndProc( var Msg: TMessage );

procedure TForm1.FormCreate(Sender: TObject);
begin
  GridXWndProc := classes.MakeObjectInstance( GridXCustomWndProc );
  GridXSaveWndProc := Pointer( GetWindowLong( GridX.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridX.Handle, GWL_WNDPROC, LongInt( GridXWndProc ) );

  GridYWndProc := classes.MakeObjectInstance( GridYCustomWndProc );
  GridYSaveWndProc := Pointer( GetWindowLong( GridY.Handle, GWL_WNDPROC ) );
  SetWindowLong( GridY.Handle, GWL_WNDPROC, LongInt( GridYWndProc ) );
end;

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridXSaveWndProc, GridX.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridY;
         GridY.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridX.Handle, GWL_WNDPROC, Longint( GridXSaveWndProc ) );
         Classes.FreeObjectInstance( GridXWndProc );
      end;
  end;
end;

procedure TForm1.GridXMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridY.SetActiveRow( GridX.GetActiveRow );
end;

procedure TForm1.GridYCustomWndProc( var Msg: TMessage );
begin
   Msg.Result := CallWindowProc( GridYSaveWndProc, GridY.Handle, Msg.Msg, Msg.WParam, Msg.LParam );
   case Msg.Msg of
      WM_KEYDOWN:
      begin
         case TWMKey( Msg ).CharCode of VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
         end;
      end;
      WM_VSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_HSCROLL:
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      WM_MOUSEWHEEL:
      begin
         ActiveControl := GridX;
         GridX.Perform( Msg.Msg, Msg.WParam, Msg.LParam );
      end;
      WM_DESTROY:
      begin
         SetWindowLong( GridY.Handle, GWL_WNDPROC, Longint( GridYSaveWndProc ) );
         Classes.FreeObjectInstance( GridYWndProc );
      end;
   end;
end;

procedure TForm1.GridYMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
begin
   GridX.SetActiveRow( GridY.GetActiveRow );
end;

解決策を提供してくれた Sertac Akyuz に感謝します。グリッドを使用して VCL フォーム アプリケーションに統合すると、それらは相互に模倣してスクロールし、選択したレコードを強調表示します。

4

5 に答える 5

3

両方のグリッドにメッセージ オーバーライドを実装している可能性があります。GridX は GridY をスクロールし、GridY は GridX をスクロールします。ブロックをフラグで囲むことで、表面的なスクロール コードを保護できます。

type
  TForm1 = class(TForm)
    [..] 
  private
    FNoScrollGridX, FNoScrollGridY: Boolean;
    [..]

procedure TForm1.GridXCustomWndProc( var Msg: TMessage );
begin
  Msg.Result := CallWindowProc(POldWndProc, gridX.Handle, Msg.Msg, Msg.wParam, Msg.lParam );

  if ( Msg.Msg = WM_VSCROLL ) then 
  begin
    if not FNoScrollGridX then
    begin
      FNoScrollGridX := True
      gridY.SetActiveRow( gridX.GetActiveRow );
      gridY.Perform( Msg.Msg, Msg.wParam, Msg.lParam );
//      SetScrollPos( gridY.Handle, SB_VERT, HIWORD( Msg.wParam ), True );
    end;
    FNoScrollGridX := False;
  end;
end;

GridY の同様のコード。ところで、SetScrollPos は必要ありません。


編集:

TForm1 = class(TForm)
  [..]
private
  GridXWndProc, GridXSaveWndProc: Pointer;
  GridYWndProc, GridYSaveWndProc: Pointer;
  procedure GridXCustomWndProc(var Msg: TMessage);
  procedure GridYCustomWndProc(var Msg: TMessage);
  [..]

procedure TForm1.FormCreate(Sender: TObject);
begin
  [..]

  GridXWndProc := classes.MakeObjectInstance(GridXCustomWndProc);
  GridXSaveWndProc := Pointer(GetWindowLong(GridX.Handle, GWL_WNDPROC));
  SetWindowLong(GridX.Handle, GWL_WNDPROC, LongInt(GridXWndProc));

  GridYWndProc := classes.MakeObjectInstance(GridYCustomWndProc);
  GridYSaveWndProc := Pointer(GetWindowLong(GridY.Handle, GWL_WNDPROC));
  SetWindowLong(GridY.Handle, GWL_WNDPROC, LongInt(GridYWndProc));
end;

procedure TForm1.GridXCustomWndProc(var Msg: TMessage);
begin
  Msg.Result := CallWindowProc(GridXSaveWndProc, GridX.Handle,
      Msg.Msg, Msg.WParam, Msg.LParam);

  case Msg.Msg of
    WM_KEYDOWN:
      begin
        case TWMKey(Msg).CharCode of
          VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        end;
      end;
    WM_VSCROLL: GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
    WM_MOUSEWHEEL:
      begin
        ActiveControl := GridY;
        GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
      end;
    WM_DESTROY:
      begin
        SetWindowLong(GridX.Handle, GWL_WNDPROC, Longint(GridXSaveWndProc));
        Classes.FreeObjectInstance(GridXWndProc);
      end;
  end;
end;

procedure TForm1.GridYCustomWndProc(var Msg: TMessage);
begin
  Msg.Result := CallWindowProc(GridYSaveWndProc, GridY.Handle,
      Msg.Msg, Msg.WParam, Msg.LParam);

  case Msg.Msg of
    WM_KEYDOWN:
      begin
        case TWMKey(Msg).CharCode of
          VK_UP, VK_DOWN, VK_PRIOR, VK_NEXT:
            GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
        end;
      end;
    WM_VSCROLL: GridX.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
    WM_MOUSEWHEEL:
      begin
        ActiveControl := GridX;
        GridY.Perform(Msg.Msg, Msg.WParam, Msg.LParam);
      end;
    WM_DESTROY:
      begin
        SetWindowLong(GridY.Handle, GWL_WNDPROC, Longint(GridYSaveWndProc));
        Classes.FreeObjectInstance(GridYWndProc);
      end;
  end;
end;
于 2010-07-21T19:58:58.513 に答える
3

部分的ですが、完全に機能するソリューションを取得しました(少なくとも2つのTMemoに対して)...

つまり、一方の TMemo の変更のみをリッスンし、もう一方の TMemo では変更をリッスンしないためです...

何が行われるかに依存しないため、完全に機能することを意味します...

同じ水平スクロール値を一方のメモにもう一方のメモに配置するのと同じくらい簡単です...

メッセージとは関係ありませんが、メッセージ WM_HSCROLL などをトラップして有効な解決策を得ようとしていたので... 有効なのでコードを残しました...後で改善しようと思います...たとえば、トラップのみWM_PAINT、または他の方法で...しかし、今のところ、それが機能するので、私は持っているままにします...そして、私はどこにももっと良いものを見つけませんでした...

動作するコードは次のとおりです。

// On private section of TForm1
Memo_OldWndProc:TWndMethod; // Just to save and call original handler
procedure Memo_NewWndProc(var TheMessage:TMessage); // New handler

// On implementation section of TForm1    
procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo_OldWndProc:=Memo1.WindowProc; // Save the handler
     Memo1.WindowProc:=Memo_NewWndProc; // Put the new handler, so we can do extra things
end;

procedure TForm1.Memo_NewWndProc(var TheMessage:TMessage);
begin
     Memo_OldWndProc(TheMessage); // Let the scrollbar to move to final position
     Memo2.Perform(WM_HSCROLL
                  ,SB_THUMBPOSITION+65536*GetScrollPos(Memo1.Handle,SB_HORZ)
                  ,0
                  ); // Put the horizontal scroll of Memo2 at same position as Memo1
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
     Memo1.WindowProc:=Memo_OldWndProc; // Restore the old handler
end;

スクロールして変更するすべての方法で機能します...

ノート:

  • すべてのメッセージをトラップするのは恐ろしいことですが、少なくとも機能します...
  • これは、同期された水平スクロールバーを持つ 2 つの TMemos を持つ私の最初の成功した試みです...
  • したがって、誰かがそれを少し改善できる場合 (すべてのメッセージをトラップするわけではありません)、それを実行して投稿してください。
  • メモ 1 をメモ 2 バーと水平同期させるだけで、メモ 2 をメモ 1 と同期させることはできません。
  • 上、下、左、右、マウスホイールなど、好きなキーを押してください。

私はそれを改善しようとします:Memo2で何かをするとき、Memo1のスクロールはまだ同期しています...

TMemoだけでなく、ScrollBarを持つほとんどすべてのコントロールで機能すると思います...

于 2012-11-05T13:02:14.703 に答える
2

I found a solution... i know it is quite tricky... but at least it is fully functional...

Instead of trying to hide the horizontal scroll bar... i make it to be displayed out of visible area, so it can not be seen by user...

The tricky part:

  • Put a TPanel where the TMemo is and put the TMemo inside the TPanel
  • Hide TPanel borders, put BorderWith as 0, and all Bevel to bvNone/bkNone
  • Configure TMemo Align to alTop, not to alClient, etc...
  • Handle TPanel.OnResize to make TMemo.Height bigger than TPanel.Height as much as Horizontal scrollbar height (by the moment i use a constant value of 20 pixels, but i would like to know how to get the real value)

That's it... done!!! The horizontal scroll bar is out of visible area... you can put where you want the TPanel, give it the size you want... that horizontal scrollbar will not be seen by user and it is not hidden, so GetScrollPos will work properly... tricky i know, but fully functional.

Here is the full code to archive that:

On interface section, before your TForm declaration, so your TForm will see this new TMemo class instead of normal one:

type
    TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
    private
       BusyUpdating:Boolean; // To avoid circular stack overflow
       SyncMemo:TMemo; // To remember the TMemo to be sync
       Old_WindowProc:TWndMethod; // To remember old handler
       procedure New_WindowProc(var Mensaje:TMessage); // The new handler
    public
       constructor Create(AOwner:TComponent);override; // The new constructor
       destructor Destroy;override; // The new destructor
    end;

On implementation section anywhere you preffer:

constructor TMemo.Create(AOwner:TComponent); // The new constructor
begin
     inherited Create(AOwner); // Call real constructor
     BusyUpdating:=False; // Initialize as not being in use, to let enter
     Old_WindowProc:=WindowProc; // Remember old handler
     WindowProc:=New_WindowProc; // Replace handler with new one
end;

destructor TMemo.Destroy; // The new destructor
begin
     WindowProc:=Old_WindowProc; // Restore the original handler
     inherited Destroy; // Call the real destructor
end;

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
       or
         BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

Also on implementation section anywhere you preffer:

procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
     Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
end;

procedure TForm1.pnlMemo2Resize(Sender: TObject);
begin
     Memo2.Height:=pnlMemo2.Height+20; // Make height enough big to cause horizontal scroll bar be out of TPanel visible area, so it will not be seen by the user
end;

Thas's it folks! I know it is quite tricky, but fully functional.

Please note that i have changed on New_WindowProc the order of evaluating the OR conditions... it is just to improve speed for all other messages, so delay as less as possible all the messages treatment.

Hope sometime i will know how to replace such 20 by the real (calculated or readed) TMemo horizontal scroll bar height.

于 2012-11-08T08:13:45.557 に答える
2

私が言ったように...

ここでは、効率、クリーンなコード、双方向の点でより良い解決策 (最終的な解決策ではありません) です...いずれかを変更すると、もう一方に影響します...

コードのコメントを読んで、各文が何をするのかを理解してください...かなりトリッキーです...しかし、主な考え方は以前と同じです...他のTMemo水平スクロールバーを、ユーザーがいるTMemoにあるように設定します動作中... ユーザーが何をしても、マウスを動かしてテキストを選択し、左、右、ホーム、終了キーを押し、マウスの水平ホイールを使用し (すべてのホイールにあるわけではありません)、スクロールバーをドラッグし、水平の任意の部分を押しますスクロールバーなど...

主なアイデアは...オブジェクトを再描画する必要があるため、このオブジェクトと同じ水平スクロールバーを他のオブジェクトに配置することです...

この最初の部分は、TMemo クラスに何かを追加するだけです。新しい派生クラスを作成するだけですが、クラス名は同じですが、宣言されたユニットに対してのみです。

これをインターフェイス セクションの TForm 宣言の前に追加すると、TForm は通常のクラスではなく、この新しい TMemo クラスを認識します。

type
    TMemo=class(StdCtrls.TMemo) // Just to add things to TMemo class only for this unit
    private
       BusyUpdating:Boolean; // To avoid circular stack overflow
       SyncMemo:TMemo; // To remember the TMemo to be sync
       Old_WindowProc:TWndMethod; // To remember old handler
       procedure New_WindowProc(var Mensaje:TMessage); // The new handler
    public
       constructor Create(AOwner:TComponent);override; // The new constructor
       destructor Destroy;override; // The new destructor
    end;

この次の部分は、その新しい TMemo クラスの以前の宣言の実装です。

これを実装セクションの好きな場所に追加します。

constructor TMemo.Create(AOwner:TComponent); // The new constructor
begin
     inherited Create(AOwner); // Call real constructor
     BusyUpdating:=False; // Initialize as not being in use, to let enter
     Old_WindowProc:=WindowProc; // Remember old handler
     WindowProc:=New_WindowProc; // Replace handler with new one
end;

destructor TMemo.Destroy; // The new destructor
begin
     WindowProc:=Old_WindowProc; // Restore the original handler
     inherited Destroy; // Call the real destructor
end;

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
       or
         (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

最後の部分では、各 TMemo に、同期する必要がある他のメモが何であるかを伝えます。

実装セクションで、Form1 Create イベントに次のようなものを追加します。

procedure TForm1.FormCreate(Sender: TObject);
begin
     Memo1.SyncMemo:=Memo2; // Tell Memo1 what TMemo must sync (Memo2)
     Memo2.SyncMemo:=Memo1; // Tell Memo2 what TMemo must sync (Memo1)
end;

特別な新しい TMemo クラスに SyncMemo メンバーを追加したことを思い出してください。これは、このためだけに存在し、お互いに他のメンバーを伝えます。

これが完全に機能するように、両方の TMemo jsut を少し構成します。

  • 両方の TMemo スクロール バーを表示する
  • 両方の Tmemo で WordWrap を false にする
  • たくさんのテキスト (どちらも同じ)、長い行、たくさんの行を入れます

実行して、両方の水平スクロールバーが常に同期していることを確認してください...

  • 一方の水平スクロールバーを動かすと、もう一方の水平スクロールバーが移動します...
  • テキストを右または左、行頭または行末などに移動すると、他の SelStart がどこにあるかに関係なく、水平テキストスクロールが同期されます。

これが最終版ではない理由は次のとおりです。

  • スクロール バー (私の場合は水平のもの) を非表示にすることはできません。

hidden をエミュレートする方法、または GetScrollPos をゼロに戻さないようにする方法を誰かが知っている場合は、コメントしてください。最終バージョンで修正する必要があるのはそれだけです。

ノート:

  • 明らかに、垂直スクロールバーでも同じことができます... WM_HSCROLL を WM_VSCROLL に、SB_HORZ を SB_VERT に変更するだけです
  • 明らかに、両方に対して同時に同じことを行うことができます... SyncMemo.Perform 行を 2 回コピーし、一方に WM_HSCROLL と SB_HORZ を追加し、もう一方に WM_VSCROLL と SB_VERT を追加します。

両方のスクロールバーを同時に同期するための New_WindowProc プロシージャの例を次に示します。怠惰な人や、コピー アンド ペーストのような人向けです。

procedure TMemo.New_WindowProc(var Mensaje:TMessage);
begin
     Old_WindowProc(Mensaje); // Call the real handle before doing anything
     if  BusyUpdating // To avoid circular stack overflow
       or
         (not Assigned(SyncMemo)) // If not yet set (see TForm1.FormCreate bwlow)
       or
         (WM_PAINT<>Mensaje.Msg) // If not when need to be repainted to improve speed
     then Exit; // Do no more and exit the procedure
     BusyUpdating:=True; // Set that object is busy in our special action
     SyncMemo.Perform(WM_HSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_HORZ),0); // Send to the other TMemo a message to set its horizontal scroll as it is on this TMemo
     SyncMemo.Perform(WM_VSCROLL,SB_THUMBPOSITION+65536*GetScrollPos(Handle,SB_VERT),0); // Send to the other TMemo a message to set its vertical scroll as it is on this TMemo
     BusyUpdating:=False; // Set that the object is no more busy in our special action
end;

誰かが非表示のスクロールバーと GetScrollPos がゼロを返す問題を解決できることを願っています!!!

于 2012-11-06T13:18:28.880 に答える
1

GetSystemMetricsとに感謝しますSM_CYHSCROLLが、それだけでは十分ではありません... あと 3 ピクセルが必要です...

だから私はちょうど使用します:GetSystemMetrics(SM_CYHSCROLL)+3

注: このようなピクセルのうちの 2 つは、値を持つ親パネルを持っている可能性がありますが、私にはBevelWidth値があり、値が含まれていない可能性があります。しかし、余分なピクセルは理由がわかりません。1BevelInnerBevelOuterbvNone

どうもありがとう。

お望みであれば、それらを 1 つの大きなポストに結合するだけですが、混合しない方がよいと思います。

「Sertac Akyuz」への回答(ここで申し訳ありませんが、質問の隣に投稿する方法がわかりません):

  • 見つけた解決策をここに置きます...私の意図はそれをスクラッチパッドとして使用することではありませんでした...投稿を書く数秒前に解決策を発見しました
  • 同じ投稿を何度も編集するよりも、古い投稿を見る方が良いと思います...それは、他の人に正確な解決策を知らせず、そのような解決策に到達する方法も知らせます。
  • 「魚をあげるより、釣り方を教える」みたいなのが好きです。
  • この質問のタイトルがまさに私がやろうとしていたことであるという理由だけで、私は新しい質問を開きませんでした

重要: スクロールは発生するがメッセージが表示されない場合があるため、完全な解決策はメッセージのキャプチャでは実行できないことがわかりましたWM_VSCROLL, WM_HSCROLL(のみWM_PAINT)... マウスでテキストを選択することに関連しています... どのように見えるかを説明させてくださいそれは動作中です...最後の視覚的な行の終わり近くで開始し、マウスを少し下に移動し、次にマウスの移動を停止してマウスボタンを押したままにします...何もせずに(マウスは動かず、キーアップもキーダウンもありません、マウス ボタンの変更がないなど...) TMemo はテキストの最後に到達するまで下にスクロールしています...マウスが視線の右端近くにあり、右に移動すると、水平スクロールでも同じことが起こります...反対も同じです指示...そのようなスクロールはメッセージを介さずWM_VSCROLL WM_HSCROLL、のみWM_PAINT(少なくとも私のコンピューターでは)...グリッドでも同じことが起こります。

于 2012-11-21T14:12:27.087 に答える