Source file: /~heha/messtech/motor.zip/SRC/MOTORST.PAS

{$A+,B-,G+,F-,K+,W-}		{Standard-Codeerzeugung}
{$P-,T-,V+,X+}			{Compiler-Prüfungen}
{ $D+,I+,L+,Q+,R+,S+,Y+}         {Debug-Codeerzeugung}
{$D-,I-,L-,Q-,R-,S-,Y-}        {Release-Codeerzeugung}
{$N-,E-}			{TReal=Real, hier kein Kopro erforderlich}
{$M 8192,8192}
program MotorSt;

{$R MotorSt.res}
{$D Schrittmotor-DDE-Server}
{$C Moveable Preload Discardable}
{$S $8000}	{Units automatisch gruppieren, weniger Far-Calls und Fixups}

uses WinProcs,WinTypes,OWindows,ODialogs,Win31,DdeMl,Strings,
 mpk3d,smsh,smsp,smsdde,Bereich,StepDlg,Grenzen,LoadSave,MotorSel;


{*********************************}
{Hauptfenster-Dialogobjekt mit DDE}
{*********************************}
type
 PMain=^TMain;
 TMain=object(TDlgWindow)
  Synced: Bool;		{Synchronzustand, DDE-abfragbar}
  InMove: Bool;		{In-Bewegung, DDE-abfragbare Variable}
  MStrom: Bool;		{Status: Motorspulen bestromt oder stromfrei}
			{bzw. Bremse angezogen oder nicht}
  InInit: Bool;		{DDE-Funktion INIT in Ausführung}
  MovReq: Bool;		{1 Move-Befehl in Warteschlange (NextPos)}
  CurPos: TReal;
  NextPos:TReal;
  {Dialog-Elemente}
  SbGrob: PScrollBar;			{Visualisierung der Motorposition}
  SbFein: PScrollBar;
  LastFocusedEdit: Word;	{Zuletzt fokussiertes Editfenster-ID}
  {DDE-Variablen}
  DdeServ: PMyDDE;			{initialisierte Instanz}
  PortBasis: Word;
  UseBrake: Bool;

  {Methoden}
  constructor Init(AParent:PWindowsObject; AName:PChar);
  destructor Done; virtual;
  function GetClassName:PChar; virtual;
  procedure SetupWindow; virtual;
  function CanClose:Boolean; virtual;
  procedure WMInitMenu(var Msg:TMessage); virtual WM_First+WM_InitMenu;
  {Menükommandos}
  procedure CMEnde(var Msg:TMessage); virtual CM_First+CM_Ende;
  procedure CMConnect(var Msg:TMessage); virtual CM_First+CM_Connect;
  procedure CMBereich(var Msg:TMessage); virtual CM_First+CM_Bereich;
  procedure CMGrenzen(var Msg:TMessage); virtual CM_First+CM_Grenzen;
  procedure CMSaveSettings(var Msg:TMessage); virtual CM_First+CM_Save;
  procedure CMHilfeIndex(var Msg:TMessage); virtual CM_First+CM_HelpIndex;
  procedure CMHilfeDde(var Msg:TMessage); virtual CM_First+CM_HelpDde;
  procedure CMAbout(var Msg:TMessage); virtual CM_First+CM_About;
  {Dialog-Kommandos}
  procedure IDSbGrob(var Msg:TMessage); virtual ID_First+ID_SbGrob;
  procedure IDSbFein(var Msg:TMessage); virtual ID_First+ID_SbFein;
  procedure IDRefFahrt(var Msg:TMessage); virtual ID_First+ID_RefFahrt;
  procedure IDStop(var Msg:TMessage); virtual ID_First+ID_Stop;
  procedure IDFree(var Msg:TMessage); virtual ID_First+ID_Free;
  procedure IDEditAbs(var Msg:TMessage); virtual ID_First+ID_AbsWert;
  procedure IDEditRel(var Msg:TMessage); virtual ID_First+ID_RelWert;
  procedure IDGoto(var Msg:TMessage); virtual ID_First+ID_Goto;
  procedure IDSetNull(var Msg:TMessage); virtual ID_First+ID_Nullpunkt;
  {DDE-Callbacks}
  procedure WMConnect(var Msg:TMessage); virtual WM_First+WM_Connect;
  procedure WMPoke(var Msg:TMessage); virtual WM_First+WM_Poke;
  procedure WMExecute(var Msg:TMessage); virtual WM_First+WM_Execute;
  procedure WMRequest(var Msg:TMessage); virtual WM_First+WM_Request;
  {VxD-Callback}
  procedure WMMovEnd(var Msg:TMessage); virtual WM_First+WM_MovEnd;
  {Rufe aus anderen Fenstern}
  procedure WMProceed(var Msg:TMessage); virtual WM_First+WM_Proceed;
  procedure WMAssign(var Msg:TMessage); virtual WM_First+WM_Assign;
  {Interne Funktionen}
  procedure SetTextStrings(Z: TReal);
  function DoReffahrt:boolean;
  function MoveMotorQuiet(nach: TReal):boolean;
  function MoveMotor(nach: TReal):boolean;
  procedure SetScrollbars(nach: TReal);
  procedure EnableButtons;
  procedure SetNewAlias;
  procedure SetInMove(AValue:Bool);
  procedure SetSynced(AValue:Bool);
  procedure SetMStrom(AValue:Bool);
  function AssignMotor(NewUnit: Byte):boolean;
 end;

constructor TMain.Init;
 begin
  inherited Init(AParent,AName);
  SbGrob:=New(PScrollBar,InitResource(@self,ID_SbGrob));
  SbFein:=New(PScrollBar,InitResource(@self,ID_SbFein));
  InMove:=false;
  MovReq:=false;
  Synced:=false;
  MStrom:=false;
  InInit:=false;
  UseBrake:=true;
 end;

destructor TMain.Done;
 begin
  Dispose(SbGrob,Done);
  Dispose(SbFein,Done);
  inherited Done;
 end;

function TMain.GetClassName:PChar;
 begin
  GetClassName:=AppName;
 end;

procedure TMain.SetupWindow;
 var
  NewUnit:Byte;
  EC: Integer;
  S:TS31;
 var
  Line: array[byte]of Char;
  i: Integer;
 begin
  inherited SetupWindow;
{Icon setzen}
  SetClassWord(HWindow,GCW_hIcon,LoadIcon(hInstance,MakeIntResource(IDC_Main)));
  NewUnit:=GetPrivateProfileInt(Section,'UnitUsed',0,Profile);
  LoadSettings;
{Das Relais für die Hardwareendschalter-Abschaltung muß ständig eingeschaltet
 bleiben. Das wird NICHT vom MPK3D erledigt, da es sich hierbei um eine
 Zusatzschaltung dieses Labormeßplatzes handelt}
  PortBasis:=$340;
  GetPrivateProfileString('386enh','MPK3DPort',
   '340',@S[1],sizeof(S)-1,'system.ini');
  S[0]:='$';
  Val(S,PortBasis,EC);
  Port[PortBasis+7]:=0;			{Relais und Motor AUS}
  AssignMotor(NewUnit);			{Bei Versagen ist keiner da}
  {DDE weiter initialisieren}
  DdeServ:=New(PMyDDE,Init(HWindow));
  SetNewAlias;	{Alias je nach aktivem Motor setzen}
  SbFein^.SetRange(-SBFaktor,SBFaktor);	{Fein-Rollbalken-Parameter setzen}
  SbGrob^.SetRange(Trunc(LeftBound+0.99),Trunc(RightBound-0.01));
{Grob-Rollbalken-Ränder derart setzen, daß niemals Überläufe auftreten}
  SetScrollbars(0.0);
{Rollbalken mittig positionieren}
  EnableButtons;
 end;

procedure TMain.WMInitMenu(var Msg:TMessage);
{Aufruf vom System vor dem Öffnen des Menüs:
 der "Bereich"-Menüpunkt muß ggf. grau dargestellt werden}
 var
  WEnable:Word;
 begin
  if Synced then WEnable:=MF_Enabled
  else WEnable:=MF_Grayed;
  EnableMenuItem(GetMenu(HWindow),CM_Bereich,WEnable or MF_ByCommand);
 end;

procedure TMain.CMEnde(var Msg:TMessage);
 begin
  SendMessage(HWindow,WM_Close,0,0);
 end;

procedure TMain.CMBereich(var Msg:TMessage);
{Bereich abfahren: 2 Dialogfenster hintereinander (falls die erste mit OK
 beantwortet wurde)}
 begin
  if Application^.ExecDialog(New(PBereichDlg,
     Init(@Self,MakeIntResource(ID_Bereich))))=ID_OK then
   Application^.ExecDialog(New(PStepDlg,
    Init(@Self,MakeIntResource(ID_StepDlg))));
 end;

procedure TMain.CMConnect(var Msg:TMessage);
{Nicht DDE! Hiermit wird ein neuer Motor ausgewählt}
 begin
  if Application^.ExecDialog(New(PMotorSelDlg,
     Init(@Self,MakeIntResource(ID_Connect))))=ID_OK then begin
   Versatz:=0.0;
   SetScrollbars(0.0);	{rücksetzen}
   SetTextStrings(0.0);	{Text auf Fragezeichen}
   SetNewAlias;		{für DDE}
   EnableButtons;
  end;
 end;

procedure TMain.CMGrenzen(var Msg:TMessage);
{Neue Grenzen festlegen}
 begin
  if Application^.ExecDialog(New(PGrenzenDlg,
     Init(@Self,MakeIntResource(ID_Grenzen))))=ID_OK then begin
{Wegen der neuen Grenzen müssen die Rollbalken mit neuen Grenzen versorgt
 werden. Hierbei genügt der Grob-Rollbalken.}
   SbGrob^.SetRange(Trunc(LeftBound+0.99),Trunc(RightBound-0.01));
   if UnitUsed<>0 then
    VxDsupC(SM_SetGear,UnitUsed,360*Faktor,Round(SchrittPU*256));
  end;
 end;

procedure TMain.CMSaveSettings(var Msg:TMessage);
{WasserMP.INI schreiben}
 begin
  if SaveSettings=false then	{bei Fehlern meckern!}
   Hinweis1(HWindow,ST_BadProfile,Profile);
 end;

procedure TMain.CMHilfeIndex(var Msg:TMessage);
 begin
  WinHelp(HWindow,HelpFileName,HELP_Index,0);
 end;

procedure TMain.CMHilfeDde(var Msg:TMessage);
 begin
  WinHelp(HWindow,HelpFileName,HELP_Context,CM_HelpDde);
 end;

procedure TMain.CMAbout(var Msg:TMessage);
 begin
  Application^.ExecDialog(New(PDialog,Init(@Self,MakeIntResource(ID_About))));
 end;

function TMain.CanClose;
{Aufruf beim Beenden von MOTORST: Warnung ausgeben bei aktiver DDE-Verbindung}
 begin
  CanClose:=false;
  if IsDlgButtonChecked(HWindow,ID_DdeVerb)=1 then begin
   if HinweisFrage(HWindow,ST_BreakDde,Mem[0:0], {nil via VAR-Parameter}
    MB_IconQuestion or MB_OKCancel)<>IDOK then exit;
  end;
  WinHelp(HWindow,HelpFileName,HELP_Quit,0);
  Dispose(DdeServ,Done);
  AssignMotor(0);	{Abmelden}
  CanClose:=true;
 end;

procedure TMain.SetNewAlias;
{Neues DDE-Alias-Topic setzen, wird bei jedem Motorwechsel aufgerufen}
 const
  OldUnit: Byte=0;
  S:array[0..6]of Char='MotorX';
 begin
  if UnitUsed<>OldUnit then begin
   if UnitUsed<>0 then begin
    S[5]:=Chr(UnitUsed+Ord('W'));	{X,Y oder Z setzen}
    DdeServ^.SetAlias(S);
   end else begin
    DdeServ^.SetAlias(nil);		{kein Alias!}
   end;
   OldUnit:=UnitUsed;
  end;
 end;

procedure TMain.SetInMove(AValue:Bool);
{Gesonderter Schreibzugriff auf DDE-kontrollierte Variable}
 begin
  if InMove<>AValue then begin
   InMove:=AValue;
   DdeServ^.Advise(2);
  end;
 end;

procedure TMain.SetSynced(AValue:Bool);
{Gesonderter Schreibzugriff auf DDE-kontrollierte Variable}
 begin
  if Synced<>AValue then begin
   Synced:=AValue;
   DdeServ^.Advise(3);
  end;
 end;

procedure TMain.SetMStrom(AValue:Bool);
{Gesonderter Schreibzugriff auf DDE-kontrollierte Variable}
 begin
  if MStrom<>AValue then begin
   MStrom:=AValue;
  end;
  if MStrom=false then SetSynced(false);
 end;

{Diese Funktion meldet den Motor an und ggf. den alten Motor ab
 Mit 0 wird der Motor nur abgemeldet!
 Funktion liefert TRUE, wenn Anmeldung okay
 Solcherart Funktionen sind übrigens typisch für SDI-Programme}
function TMain.AssignMotor(NewUnit: Byte):boolean;
 begin
  AssignMotor:=false;
  if (UnitUsed<>0) then begin
   SetMStrom(false);
   VxDsupC(SM_Stop,UnitUsed,0,0);
   VxDsupC(SM_UnAssign,UnitUsed,0,0);
   UnitUsed:=0;
  end;
  if MPK3D_Entry<>nil then begin
   if NewUnit<>0 then begin
    if VxDsupC(SM_Assign,NewUnit,0,0)=0 then begin
     UnitUsed:=NewUnit;
     VxDsupC(SM_SetPostMsg,UnitUsed,LongInt(@PostMessage),
      MakeLong(HWindow,WM_MovEnd));
     VxDsupC(SM_SetGear,UnitUsed,360*Faktor,Round(SchrittPU*256));
     if UseDreieck then
      VxDsupC(SM_SetHWE,UnitUsed,0,1 shl 17);	{Ggf. Dreiecksbetrieb}
     AssignMotor:=true;
    end;
   end else begin
    UnitUsed:=0;
    AssignMotor:=true;	{0 ist immer okay!}
   end;
  end;
 end;

procedure TMain.SetTextStrings(Z: TReal);
{Anzeige in den Editfenstern (absolute sowie relative Position)
 auf Z aktualisieren, hierbei erfolgt KEIN DDE-Advise!
 Ein Fragezeichen wird angezeigt, wenn die Position ungültig ist}
 var
  S: TS31;
 begin
  if Synced then
   Real2S(Z,3,S)
  else
   lStrCpy(S,STQuest);
  SetDlgItemText(HWindow,ID_AbsWert,S);
  if Synced then
   Real2S(Z+Versatz,3,S)
  else
   lStrCpy(S,STQuest);
  SetDlgItemText(HWindow,ID_RelWert,S);
  if LastFocusedEdit<>0 then
   SendDlgItemMessage(HWindow,LastFocusedEdit,EM_SetSel,0,MakeLong(0,$FFFF));
 end;

procedure TMain.IDSbGrob(var Msg:TMessage);
{Beim Bewegen des Thumbs mit der Maus sollen die Anzeige (Editfenster)
 mitlaufen; beim Loslassen schließlich soll die Bewegung dahin initiiert
 werden. Dasselbe gilt beim Loslassen der Cursortaste
 Außerdem wird LastFocusedEdit zurückgesetzt, damit der Knopf "Zur Position"
 eine Fehlermeldung auslöst (nun, man könnte ihn auch deaktivieren...)}
 var
  Z:TReal;
 begin
  LastFocusedEdit:=0;
  Z:=SbGrob^.GetPosition+SbFein^.GetPosition/SBFaktor;
  SetTextStrings(Z);
  if Msg.wParam=SB_EndScroll then begin
{   MessageBeep(Word(-1));}
   if Synced then begin
    MoveMotor(Z);
   end else begin
    Hinweis1(HWindow,ST_NoReffahrt,nil);
   end;
  end;
 end;

procedure TMain.IDSbFein(var Msg:TMessage);
{Tatsächlich genügt es hier, dieselbe Routine wie bei IDSBGROB zu benutzen}
 begin
  IDSbGrob(Msg);
 end;

procedure TMain.IDRefFahrt(var Msg:TMessage);
{...beim Drücken auf den Knopf "Referenzfahrt"}
 var
  Z: TReal;
 begin
  if Synced then begin
   Z:=10.0;
   if RefSpeed>0 then Z:=-Z;
   if SchrittPU<0 then Z:=-Z;
   Z:=Z+RefPos;
   MoveMotor(Z);  {10° neben dem Referenzschalter auf der richtigen Seite}
   while InMove do ShortYield;	{Warten bis Ende}
  end else begin
   if HinweisFrage(HWindow,1,self,MB_OKCancel)<>IDOK then exit;
  end;
  SetSynced(false);
  if DoReffahrt then begin {Referenzfahrt durchführen}
   MoveMotor(0.0);	{Rückbewegung in Warteschlange stellen}
  end else begin
   Hinweis1(HWindow,ST_BadReffahrt,nil);
  end;
 end;

procedure TMain.IDStop(var Msg:TMessage);	{Knopf "Stop"}
 begin
  MovReq:=false;	{Queue leeren}
  VxDsupC(SM_Stop,UnitUsed,0,0);	{Motor anhalten, das Callback kommt}
 end;

procedure TMain.IDFree(var Msg:TMessage);	{Knopf "Frei"}
 begin
  MovReq:=false;	{Queue leeren}
  if InMove then VxDsupC(SM_Stop,UnitUsed,0,0);	{Motor anhalten}
  if VxDsupC(SM_Free,UnitUsed,0,0)=0 then begin	{Motor freischalten}
   SetMStrom(false);
   Port[PortBasis+7]:=0;
   EnableButtons;
  end;
 end;

procedure TMain.WMConnect(var Msg:TMessage);
{DDE-Verbindung, wParam enthält Zustand, ob DDE aktiv oder nicht (mehr)}
 begin
  CheckDlgButton(HWindow,ID_DdeVerb,Msg.wParam);
{Rückgaben von Dialogfenstern sind nur so möglich!}
  SetWindowLong(hWindow,DWL_MsgResult,1);
 end;

procedure TMain.WMPoke(var Msg:TMessage);
{DDE-Poke auf ein Datenelement (nur CF_Text),
 wParam=Nummer (1-basiert), lParam=Stringzeiger}
 var
  Z:TReal;
 begin
  Msg.Result:=DDE_fNotProcessed;
  case Msg.wParam of
   1: if S2Real(PChar(Msg.lParam),Z) then
       if InInit then Msg.ResultLo:=DDE_fBusy
       else if MoveMotorQuiet(Z-Versatz) then
       Msg.ResultLo:=DDE_fAck;

   2,3: ;	{Kein POKE für Variablen InMove und Synced zulassen!}
  end;
  SetWindowLong(hWindow,DWL_MsgResult,Msg.Result);
 end;

procedure TMain.WMExecute(var Msg:TMessage);
{DDE-Execute (nur CF_Text), lParam=Stringzeiger}
 var
  Z: TReal;
 begin
  Msg.Result:=DDE_fNotProcessed;
  if (StrLIComp(PChar(Msg.lParam),'Move',4)=0)
  and S2Real(@(PChar(Msg.lParam)[4]),Z) then begin
   if InInit then Msg.ResultLo:=DDE_fBusy
   else if MoveMotorQuiet(Z-Versatz)
   then Msg.ResultLo:=DDE_fAck
  end else if lStrCmpi(PChar(Msg.lParam),'Sync')=0 then begin
   if InInit then Msg.ResultLo:=DDE_fBusy
   else if DoReffahrt 	{Nur Referenzfahrt (sozusagen "still")}
   then Msg.ResultLo:=DDE_fAck
  end else if lStrCmpi(PChar(Msg.lParam),'Init')=0 then begin
   InInit:=true;
   Versatz:=0.0;	{einzige Möglichkeit, den Versatz zurückzustellen}
   IDRefFahrt(Msg);	{dasselbe wie beim Drücken der Taste tun}
   while InMove or MovReq do ShortYield; {Zurückkehren erst, wenn fertig!}
   InInit:=false;
   if Synced then
    Msg.ResultLo:=DDE_fAck;	{OK melden, wenn synchronisiert}
  end else if lStrCmpi(PChar(Msg.lParam),'Wait')=0 then begin
   while InMove or MovReq do ShortYield;
   Msg.ResultLo:=DDE_fAck;	{immer OK melden}
  end else if lStrCmpi(PChar(Msg.lParam),'Stop')=0 then begin
   if VxDsupC(SM_Stop,UnitUsed,0,0)=0
   then Msg.ResultLo:=DDE_fAck
  end else if lStrCmpi(PChar(Msg.lParam),'Free')=0 then begin
   if VxDsupC(SM_Free,UnitUsed,0,0)=0 then begin
    SetMStrom(false);
    Port[PortBasis+7]:=0;
    EnableButtons;
    Msg.ResultLo:=DDE_fAck;
   end;
  end;
  SetWindowLong(hWindow,DWL_MsgResult,Msg.Result);
 end;

procedure TMain.WMRequest(var Msg:TMessage);
{DDE-Request auf ein Datenelement (nur CF_Text),
 wParam=Nummer (1-basiert), lParam=Stringzeiger
 Die zur Verfügung gestellte Stringlänge ist hierbei 32 Bytes (TS31)}
 begin
  Msg.Result:=1;
  case Msg.wParam of
   1: Real2S(CurPos+Versatz,3,PChar(Msg.lParam));
   2: wvsprintf(PChar(Msg.lParam),STDez,InMove);
   3: wvsprintf(PChar(Msg.lParam),STDez,Synced);
   else Msg.ResultLo:=0;
  end;
  SetWindowLong(hWindow,DWL_MsgResult,Msg.Result);
 end;

{Vom VxD gerufene Bewegungsende-Routine}
procedure TMain.WMMovEnd(var Msg:TMessage);
 var
  S:TS31;
 begin
  if MStrom then Port[PortBasis+7]:=3;	{Relais EIN}
  if UseBrake then Port[PortBasis+7]:=Port[PortBasis+7]and not 1;{Motor AUS}
  if Msg.lParam<>INVPOS then begin
   SetSynced(true);
   CurPos:=Msg.lParam/Faktor; DdeServ^.Advise(1);
   SetScrollbars(CurPos);
   SetTextStrings(CurPos);
{   wvsprintf(S,'Msg.lParam (Position) = %ld',Msg.lParam);
   MessageBox(HWindow,S,'Test WMMovEnd',0);}
  end else SetSynced(false);
  SetInMove(false);
  if MovReq then begin
   MoveMotor(NextPos);
   MovReq:=false;
  end else begin
   CheckDlgButton(HWindow,ID_InFahrt,0);	{Lämpchen AUS}
   EnableButtons;		{Ggf. Schalter aktivieren}
  end;
  if InformDlg<>0 then		{Kindfenster informieren}
   SendMessage(InformDlg,WM_MovEnd,Msg.wParam,Msg.lParam);
 end;

{Vom untergeordneten Dialogfenster gerufene Routine, in wParam
steht ein BOOL, ob eine Initialisierung stattfinden soll}
procedure TMain.WMProceed(var Msg:TMessage);
 const
  MemPos: TReal=0.0;		{Statische Variable, die nur hier gültig ist}
 begin
  Msg.Result:=0;
  MemPos:=MemPos+BereichSchritt;
  if Msg.wParam<>0 then		{Initialisierung (Fahrt zum Startpunkt)?}
   MemPos:=BereichVon;		{Ja!}
{Je nach Fahrtrichtung Endebedingung prüfen}
  if (BereichSchritt<0) and (MemPos>=BereichBis)
  or (BereichSchritt>0) and (MemPos<=BereichBis) then begin
   if MoveMotor(MemPos-Versatz) then
    Msg.ResultLo:=1;		{als auch über "Umweg"}
  end;
  SetWindowLong(hWindow,DWL_MsgResult,Msg.Result);
 end;

{Vom untergeordneten Dialogfenster gerufene Routine zum Neuzuweisen
 des Motors, wParamLo enthält die neue Motornummer}
procedure TMain.WMAssign(var Msg:TMessage);
 begin
  Msg.Result:=LongInt(AssignMotor(Msg.wParamLo));
  SetWindowLong(hWindow,DWL_MsgResult,Msg.Result);
 end;

procedure TMain.IDEditAbs(var Msg:TMessage);
{Letzte Fokuszuweisung merken für richtige Wirkung des Schalters
 "Zur Position", wParam enthält Fenster-ID}
 begin
  if Msg.lParamHi=EN_SetFocus then LastFocusedEdit:=Msg.wParam;
  Msg.Result:=0;
 end;

procedure TMain.IDEditRel(var Msg:TMessage);
{Letzte Fokuszuweisung merken für richtige Wirkung des Schalters
 "Zur Position"}
 begin
  if Msg.lParamHi=EN_SetFocus then LastFocusedEdit:=Msg.wParam;
  Msg.Result:=0;
 end;

procedure TMain.IDGoto;
{Knopf "Zur Position": Je nach zuletzt aktiviertem Dialogelement
 * relative Position anfahren oder
 * absolute Position anfahren oder
 * Fehlermeldung ausgeben}
 var
  S: TS31;
  Z,V: TReal;
 begin
  if LastFocusedEdit=ID_AbsWert then begin
   GetDlgItemText(HWindow,ID_AbsWert,S,sizeof(S));
   V:=0.0;
  end else if LastFocusedEdit=ID_RelWert then begin
   GetDlgItemText(HWindow,ID_RelWert,S,sizeof(S));
   V:=Versatz;
  end else begin
   Hinweis1(HWindow,ST_NoEditFocused,S);
   exit;
  end;
  if S2Real(S,Z) and (UnitUsed<>0) then begin
   MoveMotor(Z-V);
  end else begin
   Hinweis1(HWindow,ST_BadNumber,S);
  end;
  SetFocus(GetDlgItem(HWindow,LastFocusedEdit));
	{Zurückfokussieren, damit der User sieht, welchen Wert er setzte}
{  SendDlgItemMessage(HWindow,LastFocusedEdit,EM_SetSel,0,MakeLong(0,$FFFF));
   {und alles markieren, damit gleich ein neuer Wert eingegeben werden kann}
 end;

procedure TMain.IDSetNull;
{Knopf "Nullpunkt setzen"}
 var
  S: TS31;
  Z: TReal;
 begin
  GetDlgItemText(HWindow,ID_AbsWert,S,sizeof(S));
  if S2Real(S,Z) then begin
   if (-90<=Z) and (Z<=90) then begin
    Versatz:=-Z;
    SetTextStrings(Z);	{logischerweise wird RelPos nun 0}
    Ddeserv^.Advise(1);	{Neue Relativposition!}
   end else begin
    Hinweis1(HWindow,ST_BadZeroRange,S);
   end;
  end else begin
   Hinweis1(HWindow,ST_BadNumber,S);
  end;
 end;

procedure TMain.SetScrollbars(nach: TReal);
{Interne Routine zum Setzen der Rollbalken auf einen bestimmten Punkt,
 z.B. bei Bewegungsende}
 var
  p1,p2:Integer;
 begin
  p1:=Round(nach);		{Gerundeter Wert}
  p2:=Round((nach-p1)*SBFaktor);{Verbleibende Differenz}
  SbGrob^.SetPosition(p1);
  SbFein^.SetPosition(p2);
 end;

procedure TMain.EnableButtons;
{Generalroutine, die je nach diversen programmglobalen Flags die Tasten
 freischaltet oder sperrt. Lt. Dok. prüft EnableWindow() zuerst den
 Momentanzustand eines Fensters.}
 var
  B: Bool;
 begin
  B:=Synced;
  EnableWindow(GetDlgItem(HWindow,ID_Goto),B);
  EnableWindow(GetDlgItem(HWindow,ID_Nullpunkt),B);
  B:=InMove;
  EnableWindow(GetDlgItem(HWindow,ID_Stop),B);
  B:=UnitUsed<>0;
  EnableWindow(GetDlgItem(HWindow,ID_Reffahrt),B);
  B:=B and MStrom;
  EnableWindow(GetDlgItem(HWindow,ID_Free),B);
 end;

function TMain.DoReffahrt:boolean;
{Knopf "Referenzfahrt"}
 begin
  DoReffahrt:=false;
  if MStrom then Port[PortBasis+7]:=3;	{Motor EIN}
  SetMStrom(true);
  Port[PortBasis+7]:=1;	{Relais AUS}
  if NOMOTOR and (VxDsupC(SM_Sync,UnitUsed,INVPOS,0)=0)
  or not NoMotor and VxDsupR(SM_Sync,RefPos,RefSpeed) then begin
   CheckDlgButton(HWindow,ID_InFahrt,1);	{Lämpchen EIN}
   SetInMove(true);
   EnableButtons;
   DoReffahrt:=true;
  end;
 end;

function TMain.MoveMotorQuiet(nach: TReal):boolean;
{Motor auf neue Position setzen ohne Messagebox bei Fehler (für DDE)}
 begin
  if InMove then begin	{Gerade beschäftigt?}
   NextPos:=nach;	{Anforderung in Warte"schlange" stellen}
   MovReq:=true;
   MoveMotorQuiet:=true;	{und "okay" melden}
  end else begin
   VxDsupR(SM_SetBounds,LeftBound,RightBound);
   VxDsupR(SM_SetSA,MoveSpeed,MoveAccel);
   Port[PortBasis+7]:=3;	{Motor EIN}
   Port[PortBasis+7]:=1;	{Relais AUS}
   if VxDsupR(SM_MoveAbs,Nach,0.0) then begin
    CheckDlgButton(HWindow,ID_InFahrt,1);	{Lämpchen EIN}
    SetInMove(true);
    EnableButtons;
    MoveMotorQuiet:=true;
   end else begin
    MoveMotorQuiet:=false;
   end;
  end;
 end;

function TMain.MoveMotor(nach: TReal):boolean;
{Motor auf neue Position setzen, mit Messagebox bei Fehler}
 var
  S: TS31;
{  L: LongInt;}
 begin
{  L:=Round(nach*Faktor);
  wvsprintf(S,'Der Move-Befehl geht zu %ld',L);
  MessageBox(HWindow,S,'Motor-Start',0);}
  if MoveMotorQuiet(nach) then begin
   MoveMotor:=true;
  end else begin
   Real2S(Nach,3,S);
   Hinweis1(HWindow,ST_BadMove,S);
   MoveMotor:=false;
  end;
 end;

{*******************}
{Applikations-Objekt}
{*******************}
type
 PApp=^TApp;
 TApp=object(TApplication)
  procedure InitMainWindow; virtual;	{sonst wäre es nur ein leeres Fenster}
 end;

procedure TApp.InitMainWindow;
 begin
  inherited InitMainWindow;
  MainWindow:=New(PMain,Init(nil,MakeIntResource(100)));
 end;

var
 App:TApp;

begin
 if lStrCmpi(CmdLine,'/debug')=0 then NoMotor:=true;
 App.Init(AppName);
 App.Run;
 App.Done;
end.
Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded