Drain all available serial data before calling FlipToScreen so overlapping screen changes collapse into a single GDI render pass. Messages are checked between chunks to keep keyboard responsive. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
6.7 KiB
ObjectPascal
247 lines
6.7 KiB
ObjectPascal
unit TestMain;
|
|
|
|
{ Test application for TKPComm and TKPAnsi components. }
|
|
{ Form and all controls are created in code (no DFM required). }
|
|
{ }
|
|
{ Layout: toolbar row at top (port, settings, open/close, status), }
|
|
{ TKPAnsi terminal filling the rest of the form. Received serial data }
|
|
{ is polled from TKPComm.Input in a PeekMessage main loop; keystrokes }
|
|
{ from the terminal are sent out via TKPComm.Output. }
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, WinTypes, WinProcs, Messages,
|
|
Forms, Controls, StdCtrls, KPComm, KPAnsi;
|
|
|
|
type
|
|
TMainForm = class(TForm)
|
|
private
|
|
{ Components (owned by Self, freed automatically) }
|
|
FComm: TKPComm; { Serial communications component }
|
|
FAnsi: TKPAnsi; { ANSI terminal display }
|
|
|
|
{ Toolbar controls }
|
|
FLabelPort: TLabel; { "Port:" label }
|
|
FEditPort: TEdit; { COM port number entry }
|
|
FLabelSettings: TLabel; { "Settings:" label }
|
|
FEditSettings: TEdit; { Baud/parity/data/stop entry }
|
|
FBtnOpen: TButton; { Opens the serial port }
|
|
FBtnClose: TButton; { Closes the serial port }
|
|
FLabelStatus: TLabel; { Displays "Open" or "Closed" }
|
|
|
|
FDone: Boolean; { True when WM_QUIT received }
|
|
procedure AnsiKeyData(Sender: TObject; const Data: string);
|
|
procedure BtnCloseClick(Sender: TObject);
|
|
procedure BtnOpenClick(Sender: TObject);
|
|
procedure UpdateStatus;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
procedure Run;
|
|
end;
|
|
|
|
var
|
|
MainForm: TMainForm;
|
|
|
|
implementation
|
|
|
|
|
|
procedure TMainForm.AnsiKeyData(Sender: TObject; const Data: string);
|
|
begin
|
|
if FComm.PortOpen and (Length(Data) > 0) then
|
|
begin
|
|
try
|
|
FComm.Output := Data;
|
|
except
|
|
on E: Exception do
|
|
{ Ignore send errors from keyboard input }
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMainForm.BtnCloseClick(Sender: TObject);
|
|
begin
|
|
FComm.PortOpen := False;
|
|
UpdateStatus;
|
|
end;
|
|
|
|
|
|
procedure TMainForm.BtnOpenClick(Sender: TObject);
|
|
begin
|
|
try
|
|
FComm.CommPort := StrToInt(FEditPort.Text);
|
|
FComm.Settings := FEditSettings.Text;
|
|
FComm.PortOpen := True;
|
|
except
|
|
on E: Exception do
|
|
FAnsi.Write('Open failed: ' + E.Message + #13#10);
|
|
end;
|
|
UpdateStatus;
|
|
FAnsi.SetFocus;
|
|
end;
|
|
|
|
|
|
constructor TMainForm.Create(AOwner: TComponent);
|
|
begin
|
|
inherited CreateNew(AOwner);
|
|
|
|
Caption := 'KPComm ANSI Terminal';
|
|
Width := 660;
|
|
Height := 460;
|
|
BorderStyle := bsSingle;
|
|
|
|
{ Serial component }
|
|
FComm := TKPComm.Create(Self);
|
|
|
|
{ Row 1: Port and Settings }
|
|
FLabelPort := TLabel.Create(Self);
|
|
FLabelPort.Parent := Self;
|
|
FLabelPort.Left := 8;
|
|
FLabelPort.Top := 12;
|
|
FLabelPort.Caption := 'Port:';
|
|
|
|
FEditPort := TEdit.Create(Self);
|
|
FEditPort.Parent := Self;
|
|
FEditPort.Left := 44;
|
|
FEditPort.Top := 8;
|
|
FEditPort.Width := 32;
|
|
FEditPort.Text := '1';
|
|
|
|
FLabelSettings := TLabel.Create(Self);
|
|
FLabelSettings.Parent := Self;
|
|
FLabelSettings.Left := 88;
|
|
FLabelSettings.Top := 12;
|
|
FLabelSettings.Caption := 'Settings:';
|
|
|
|
FEditSettings := TEdit.Create(Self);
|
|
FEditSettings.Parent := Self;
|
|
FEditSettings.Left := 148;
|
|
FEditSettings.Top := 8;
|
|
FEditSettings.Width := 140;
|
|
FEditSettings.Text := '115200,N,8,1';
|
|
|
|
FBtnOpen := TButton.Create(Self);
|
|
FBtnOpen.Parent := Self;
|
|
FBtnOpen.Left := 300;
|
|
FBtnOpen.Top := 8;
|
|
FBtnOpen.Width := 65;
|
|
FBtnOpen.Height := 25;
|
|
FBtnOpen.Caption := 'Open';
|
|
FBtnOpen.OnClick := BtnOpenClick;
|
|
|
|
FBtnClose := TButton.Create(Self);
|
|
FBtnClose.Parent := Self;
|
|
FBtnClose.Left := 372;
|
|
FBtnClose.Top := 8;
|
|
FBtnClose.Width := 65;
|
|
FBtnClose.Height := 25;
|
|
FBtnClose.Caption := 'Close';
|
|
FBtnClose.Enabled := False;
|
|
FBtnClose.OnClick := BtnCloseClick;
|
|
|
|
FLabelStatus := TLabel.Create(Self);
|
|
FLabelStatus.Parent := Self;
|
|
FLabelStatus.Left := 450;
|
|
FLabelStatus.Top := 12;
|
|
FLabelStatus.Caption := 'Closed';
|
|
|
|
{ ANSI terminal }
|
|
FAnsi := TKPAnsi.Create(Self);
|
|
FAnsi.Parent := Self;
|
|
FAnsi.Left := 0;
|
|
FAnsi.Top := 38;
|
|
FAnsi.OnKeyData := AnsiKeyData;
|
|
|
|
{ Font diagnostic: write known CP437 box-drawing characters. }
|
|
{ If the OEM font is working, you should see: }
|
|
{ Line 1: single-line box top +---+ }
|
|
{ Line 2: shade + full block ░▒▓█ }
|
|
{ Line 3: single-line box bottom +---+ }
|
|
{ If you see accented letters, the font is ANSI_CHARSET instead of }
|
|
{ OEM_CHARSET. }
|
|
FAnsi.Write(#$DA#$C4#$C4#$C4#$BF' '#$B0#$B1#$B2#$DB' '#$C0#$C4#$C4#$C4#$D9#13#10);
|
|
end;
|
|
|
|
|
|
procedure TMainForm.Run;
|
|
var
|
|
Msg: TMsg;
|
|
S: string;
|
|
HasData: Boolean;
|
|
begin
|
|
Show;
|
|
FDone := False;
|
|
while not FDone do
|
|
begin
|
|
{ Process all pending Windows messages (keyboard, paint, scrollbar) }
|
|
while PeekMessage(Msg, 0, 0, 0, pm_Remove or pm_NoYield) do
|
|
begin
|
|
if Msg.message = wm_Quit then
|
|
begin
|
|
FDone := True;
|
|
Break;
|
|
end;
|
|
TranslateMessage(Msg);
|
|
DispatchMessage(Msg);
|
|
end;
|
|
|
|
if FDone then
|
|
Break;
|
|
|
|
{ Drain all available serial data before rendering. Batching }
|
|
{ many chunks into one render pass avoids per-chunk GDI overhead. }
|
|
{ Messages are checked between chunks so keyboard stays responsive.}
|
|
HasData := False;
|
|
if FComm.PortOpen then
|
|
begin
|
|
S := FComm.Input;
|
|
while (Length(S) > 0) and not FDone do
|
|
begin
|
|
FAnsi.WriteDeferred(S);
|
|
HasData := True;
|
|
{ Check for messages between chunks }
|
|
while PeekMessage(Msg, 0, 0, 0, pm_Remove or pm_NoYield) do
|
|
begin
|
|
if Msg.message = wm_Quit then
|
|
begin
|
|
FDone := True;
|
|
Break;
|
|
end;
|
|
TranslateMessage(Msg);
|
|
DispatchMessage(Msg);
|
|
end;
|
|
S := FComm.Input;
|
|
end;
|
|
end;
|
|
|
|
if FDone then
|
|
Break;
|
|
|
|
{ Tick blink (dirties rows if interval elapsed), then render }
|
|
FAnsi.TickBlink;
|
|
FAnsi.FlipToScreen;
|
|
|
|
{ Yield CPU to other apps when no serial data is flowing. }
|
|
{ PM_NOYIELD keeps message draining fast; Yield here gives other }
|
|
{ apps a timeslice only when idle. During bulk data flow, HasData }
|
|
{ stays True and the loop runs at full speed. }
|
|
if not HasData then
|
|
Yield;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TMainForm.UpdateStatus;
|
|
begin
|
|
if FComm.PortOpen then
|
|
FLabelStatus.Caption := 'Open'
|
|
else
|
|
FLabelStatus.Caption := 'Closed';
|
|
|
|
FBtnOpen.Enabled := not FComm.PortOpen;
|
|
FBtnClose.Enabled := FComm.PortOpen;
|
|
end;
|
|
|
|
|
|
end.
|