1

I am using Delphi 10.1 Berlin, VCL project. I am attempting to move multiple panels at the same time using a touchscreen and the WM_Touch messages generated from Windows. I based it off the example code from Chris Benson's blog: http://chrisbensen.blogspot.com/2009/11/touch-demo.html

And it does work to a degree, ONLY if I touch somewhere on the background form first, and then drag my finger onto the Panel. The Panel(s) will then move with my fingers if I touch the background first. From what I can tell, it seems like the TForm1.WMTouch procedure will not get called when I touch a panel that is on top of the Form. I don't receive WM_Touch messages unless I touch the form first.

In Chris' blog he calls the RegisterTouchWindow(Handle, 0); in the Form Create. Which I assume is why the procedure will get called when you touch the Form. How would I go about getting the WM_Touch procedure called whenever I touch the panel?

-I tried using the OnClick event for the panel, but that doesn't work since it doesn't seem to register a touch as an OnClick event.

-Is this something that I should use the Inherit class with? If so, how would I go about doing it?

Thank you in advance for any ideas!

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TOUCHINPUT = record
  x: Integer;
  y: Integer;
  hSource: THandle;
  dwID: DWORD;
  dwFlags: DWORD;
  dwMask: DWORD;
  dwTime: DWORD;
  dwExtraInfo: ULONG_PTR;
  cxContact: DWORD;
  cyContact: DWORD;
end;

type
    TForm1 = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;

    procedure WMTouch(var Message: TMessage); message wm_Touch;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  end;

var
  Form1: TForm1;

implementation
//==============================================================================

//======================== FORM CREATE =========================================

procedure TForm1.FormCreate(Sender: TObject);
begin
  //  inherited;
    RegisterTouchWindow(Handle, 0);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    UnregisterTouchWindow(Handle);
end;

//==============================================================================

//========================= WM TOUCH ===========================================

procedure TForm1.WMTouch(var Message: TMessage);
    //==============
    function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
      begin
        Result := Point(TouchPoint.X div 100, TouchPoint.Y div 100);
        PhysicalToLogicalPoint(Handle, Result);
      end;
     //==============
 var
    TouchInputs: array of TTouchInput;
    TouchInput: TTouchInput;
    Handled: Boolean;
    Point: TPoint;

begin
  Handled := False;
  SetLength(TouchInputs, Message.WParam);
  GetTouchInputInfo(Message.LParam, Message.WParam,
  @TouchInputs[0], SizeOf(TTouchInput));
  try
      for TouchInput in TouchInputs do
      begin
          Point := TouchPointToPoint(TouchInput);
          if PtInRect(Panel1.BoundsRect,Point) then
          begin
              label1.Caption:='Touch ID: ' + inttostr(TouchInput.dwID);
              panel1.Top:=point.Y-100;
          end;
          if PtInRect(Panel2.BoundsRect,Point) then
          begin
              label1.Caption:='Touch ID: ' + inttostr(TouchInput.dwID);
              panel2.Top:=point.Y-100;
          end;
          if PtInRect(Panel3.BoundsRect,Point) then
          begin
              label1.Caption:='Touch ID: ' + inttostr(TouchInput.dwID);
              panel3.Top:=point.Y-100;
          end;
      end;
      Handled := True;
    finally
      if Handled then
        CloseTouchInputHandle(Message.LParam)
      else
      //  inherited;
  end;
end;
end.
Ben Palmer
  • 11
  • 1

1 Answers1

3

From what I can tell, it seems like the TForm1.WMTouch procedure will not get called when I touch a panel that is on top of the Form. I don't receive WM_Touch messages unless I touch the form first.

Correct, because you are not touching the Form itself, you are touching the Panel that is on top of the Form.

In Chris' blog he calls the RegisterTouchWindow(Handle, 0); in the Form Create. Which I assume is why the procedure will get called when you touch the Form.

Yes.

How would I go about getting the WM_Touch procedure called whenever I touch the panel?

You would have to register the Panel's HWND instead of the Form's HWND. And do that for every Panel individually. This is even stated as much in the RegisterTouchWindow() documentation:

Note RegisterTouchWindow must be called on every window that will be used for touch input. This means that if you have an application that has multiple windows within it, RegisterTouchWindow must be called on every window in that application that uses touch features. Also, an application can call RegisterTouchWindow any number of times for the same window if it desires to change the modifier flags. A window can be marked as no longer requiring touch input using the UnregisterTouchWindow function.

You would also have to subclass each Panel to handle the WM_TOUCH message for each panel, since it will be sent directly to each registered Panel and not to the Form.

If you want every Panel on the Form to be touch-capable, a simple interposer class will suffice:

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TPanel = class(Vcl.ExtCtrls.TPanel)
  protected
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
    procedure WMTouch(var Message: TMessage); message WM_TOUCH;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    Panel3: TPanel;
  end;

var
  Form1: TForm1;

implementation

type
  TOUCHINPUT = record
    x: Integer;
    y: Integer;
    hSource: THandle;
    dwID: DWORD;
    dwFlags: DWORD;
    dwMask: DWORD;
    dwTime: DWORD;
    dwExtraInfo: ULONG_PTR;
    cxContact: DWORD;
    cyContact: DWORD;
  end;

procedure TPanel.CreateWnd;
begin
  inherited;
  RegisterTouchWindow(Handle, 0);
end;

procedure TPanel.DestroyWnd;
begin
  UnregisterTouchWindow(Handle);
  inherited;
end;

procedure TPanel.WMTouch(var Message: TMessage);
  function TouchPointToPoint(const TouchPoint: TTouchInput): TPoint;
  begin
    Result := Point(TouchPoint.X div 100, TouchPoint.Y div 100);
    PhysicalToLogicalPoint(Handle, Result);
  end;
var
  TouchInputs: array of TTouchInput;
  TouchInput: TTouchInput;
  Handled: Boolean;
  Point: TPoint;
begin
  Handled := False;
  SetLength(TouchInputs, Message.WParam);
  GetTouchInputInfo(Message.LParam, Message.WParam,
  @TouchInputs[0], SizeOf(TTouchInput));
  try
    for TouchInput in TouchInputs do
    begin
      Point := TouchPointToPoint(TouchInput);
      if PtInRect(BoundsRect, Point) then
      begin
        //labelX.Caption := 'Touch ID: ' + IntToStr(TouchInput.dwID);
        Top := Point.Y - 100;
      end;
      Handled := True;
    end;
  finally
    if Handled then
      CloseTouchInputHandle(Message.LParam)
    else
      inherited;
  end;
end;

end.
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • THANK YOU SO MUCH! I had a feeling that it would be something like that. I also didn't know about how to register it to the panel. Thank you for the quick and VERY detailed response! – Ben Palmer Oct 14 '19 at 16:10
  • Hi Remy, What is the individual command for registering a single panel's HWND instead of all panels? I am only wanting to control a small handful of panels. Thanks! – Ben Palmer Oct 14 '19 at 20:44
  • Then you can't use the interposer approach I demonstrated. You will have to actually derive a new class from `TPanel` and then change only the panels you are interested in to use that class while the rest use the standard `TPanel`. Otherwise, you will have to make the Form itself register only the individual panels you are interested in, and subclass them to handle the `WM_TOUCH` messages, but that gets more involved to manage at the Form level, especially when accounting for window recreations. Using a derived Panel class keeps things simpler and encapsulated. – Remy Lebeau Oct 14 '19 at 20:49