Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi Überprüfen ob Mausklick in bestimmten bereich war (https://www.delphipraxis.net/74193-ueberpruefen-ob-mausklick-bestimmten-bereich-war.html)

xZise 30. Jul 2006 10:38


Überprüfen ob Mausklick in bestimmten bereich war
 
Ich habe ein großes Image und möchte überprüfen ob der Mausklick in bestimmten Rechtecken war.

Welche Prozeduren etc würden da gehen?

Nikolas 30. Jul 2006 10:44

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
machs doch so:
Delphi-Quellcode:
x,y: Mauskoordinate; top, left, width, height: ein Zielbereich:

if (x>left) and (x<(left+width)) and (y>top) and (y<(top+height)) then showmessage('drin');

xZise 30. Jul 2006 10:48

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
So... Und jetzt 26 mal???

ich dachte da vielleicht an einen Vergleich:
Delphi-Quellcode:
for i := 0 to 25 do
  if inRect(myRect[i], X, Y) then begin
    {...}
  end;

Nikolas 30. Jul 2006 11:10

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Wo ist denn das Problem?

// Bei rectangle waren doch die ersten Koordinaten die ecke links oben und die zweiten rechts unten, oder?
Delphi-Quellcode:
Function inRect(Rect[i], X, Y): boolean;
begin
result:= false;
if (x>Rect[i].x1) and (x<(Rect[i].x2-Rect[i].x1)) and (das gleiche für y ) then result:= true;

igel457 30. Jul 2006 11:11

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Was ist denn jetzt dein Problem?

Packe doch den Boolschen ausdruck von oben in eine Funktion "inRect" und rufe diese 26 Mal auf.

EDIT: Wo war dieser Rote Kasten...

Delphi-Quellcode:
function InRect(x,y:integer;rect:TRect):boolean;
begin
  result := (x >= rect.Left) and (y >= rect.Top) and (y <= rect.Bottom) and (y >= rect.Top);
end;
Wäre einfacher...

marabu 30. Jul 2006 11:17

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Und noch ein Ansatz:

Delphi-Quellcode:
type
  TRects = array of TRect;

var
  rects: TRects;

function InRect(rects: TRects; pt: TPoint): Boolean;
var
  i: Integer;
begin
  Result := True;
  for i := Low(rects) to High(rects) do
    if PtInRect(rects[i], pt) then
      Exit;
  Result := False;
end;
Grüße vom marabu

3_of_8 30. Jul 2006 11:20

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Igels Version dürfte die schnellste sein, da sie keine Branches enthält...

Allerdings dürften die Unterschiede minimal sein, kann sogar sein, dass der Compiler sowieso das so stark optimiert, dass es gar nicht mehr auffällt.

Nicolai1234 30. Jul 2006 11:29

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Ich denke mal, das ganze bezieht sich auf dieses DonD-Spiel. Ich denke, du hast da alle 26 in einem Image oder dergleichen.
Wäre es nicht besser, bei einem Klick die Koordinaten zu nehmen und berechnen zu lassen, auf welchen Koffer man gedrückt hat?
Die Koffer sind ja immer gleich groß. Sagen wir mal 50 Pixel breit. Wenn du dann als X-Wert sowas wie 384 bekommst weißt du, dass 384 / 50 = 7 (Rest 34) ist, also der 7. Koffer in der Reihe gedrückt wurde.
Jetzt musst du nur noch den Rand "ausmessen", bei dem links jeweils der erste Koffer beginnt.
grrr... ich bin gerade erst aufgestanden und kann noch keine Texte schreiben, ich hoffe es ist klar, was ich meine.
Ich denke, diese Art wäre schneller, als alle 26 mal inRect auszuführen.

xZise 30. Jul 2006 11:33

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Okay die Funktion könnte ich mir selber schreiebn (:D), was mich am ersten asnatz gestört hat ist die "komplexibilität"...

Da bilckt man nicht so einfach durch... Da ist die "InRect" Methode besser.

Okay... Dann mache ich's so ;)


[roter Kasten]
o.O
Öhm ja, so könnte man es machen :D

Problem ist nur, dass sie nach unten hin versetzt sind... Also könnte ich die Reihe bestimmen und müsste an noch ein bisschen mehr rand dazuaddieren.

(PS: richtig geschlussfolgert :mrgreen: )[/roter Kasten]

Nikolas 30. Jul 2006 11:35

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
bei seinem Dond sind die Koffer aber nicht schön wie auf einem Schachbrett angeordnet, sondern die zweite Reihe beginnt eine halbe Kofferbreite weiter rechts als die erste. Um das auch noch zu berücksichtigen müsste man auch noch ein paar Abfragen schreiben.
Da die Koffersuche aber nur alle paar minuten mal ausgeführt werden muss, ist es hier wichtiger einen gut verständlichen Code zu schreiben, als einen schnellen.

Nicolai1234 30. Jul 2006 11:37

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Zitat:

Zitat von xZise
[roter Kasten]
o.O
Öhm ja, so könnte man es machen :D

Problem ist nur, dass sie nach unten hin versetzt sind... Also könnte ich die Reihe bestimmen und müsste an noch ein bisschen mehr rand dazuaddieren.

(PS: richtig geschlussfolgert :mrgreen: )[/roter Kasten]

Na ja, der Rand verhält sich proportional zur Reihe. In der ersten ist er sagen wir mal nur 10, in der zweiten dann 35, dann 60. Es kommt immer ein halber Koffer hinzu.
Also 10 + 25*(Spaltennummer - 1) oder so in der Art. Je nachdem, wie groß die Zahlen wirklich sind.

Probier das mal aus, ich finde, dass dieser Weg der schönste wäre. Vor allem, weil du kein Array[1..26] of Trect brauchst, die du manuell füllen musst.

xZise 30. Jul 2006 11:58

Re: Überprüfen ob Mausklick in bestimmten bereich war
 
(Das TRect könnte ich in der Zeichenrutine unterbringen :D)

Aber hier ist jetzt die Lösung des Problems!
Es funktioniert eigentlich genz gut!
Was noch fehlt, ist, dass man nicht rechts neben den Koffern klicken kann!

Delphi-Quellcode:
procedure TForm1.SpielfeldMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var row, side, col, KofferNum,prefrows : Integer;
begin
  // Zeile heruasfinden (0-3)
  row := Floor(Y / 70);
  // Rand herausfinden (5 mindestens und pro Zeile (row = 1 oder 3) sind es 32 px mehr ansonsten sind es 65 px)
  side := Floor(5 + row * 32.5);
  // Spalte herausfinden
  col := Floor((x - side) / 70);
  // dareingehen, wenn man mindestens über den linksten Koffer ist
  if col > 0 then begin
    // Vorhergende Koffer dazuaddieren
    prefrows := 0;
    if row >= 1 then
      inc(prefrows, 8); // 1. sind 8 K. +
    if row >= 2 then
      inc(prefrows, 7); // 2. sind 7 K. +
    if row >= 3 then
      inc(prefrows, 6); // 3. sind nochmal 6 K. +
    // Koffernummer (plus eins, weil der erste ohne dem plus eins 0 ist)
    KofferNum := col + prefrows + 1;
    // zu testzwecken einfach mal anzeigen !
    Label2.Caption := IntToStr(row) + ' :: ' + IntToStr(col) + ' == ' + IntToStr(KofferNum);
  end else
    Label2.Caption := IntToStr(row) + ' :: ' + IntToStr(col) + ' == ';
end;

DP-Maintenance 30. Jul 2006 12:08

DP-Maintenance
 
Dieses Thema wurde von "Phoenix" von "Object-Pascal / Delphi-Language" nach "VCL / WinForms / Controls" verschoben.
Hat eher was mit den Klassen der VLC zu tun

Root2k 23. Mär 2012 11:31

AW: Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Hi.

Ich weiß, dass dieser thread schon ein "bisschen" veraltet ist, aber vom Thema her passt das hier ganz gut zu meinem Problem.

Zunächst zu dem Eintrag von Igel:

Zitat:

Zitat von igel457 (Beitrag 502464)
Delphi-Quellcode:
function InRect(x,y:integer;rect:TRect):boolean;
begin
  result := (x >= rect.Left) and (y >= rect.Top) and (y <= rect.Bottom) and (y >= rect.Top);
end;

Da hat sich ein kleiner Fehler eingeschlichen. Korrekt sollte die Funktion so aussehen:
Delphi-Quellcode:
function InRect(x,y:integer;rect:TRect):boolean;
begin
  result := (x >= rect.Left) and (y >= rect.Top) and (y <= rect.Bottom) and (x <= rect.Right);
end;

Mein Problem bezieht sich auch auf diese Funktion:
Funktioniert das Ganze vielleicht auch irgendwie mit einem Polygon, bzw. einer beliebigen Form die durch ein Punkte-Array definiert ist?
Kann ich z.B. ermitteln wenn sich die Maus in einem Kreis befindet? Derzeit lege ich ein Rechteck außen herum, aber das ist je nach Form sehr ungenau.

Über hilfreiche Beiträge würde ich mich freuen.

DeddyH 23. Mär 2012 12:04

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Auf die Schnelle fällt mir MSDN-Library durchsuchenPtInRegion ein (für das ursprüngliche Problem hätte man BTW auch MSDN-Library durchsuchenPtInRect verwenden können, sofern ich den Thread korrekt überflogen habe). Der einzige Nachteil ist, dass Du zuerst eine entsprechende Region erzeugen (und später wieder freigeben) musst.

Namenloser 23. Mär 2012 12:14

AW: Re: Überprüfen ob Mausklick in bestimmten bereich war
 
Zitat:

Zitat von Root2k (Beitrag 1158140)
Funktioniert das Ganze vielleicht auch irgendwie mit einem Polygon, bzw. einer beliebigen Form die durch ein Punkte-Array definiert ist?
Kann ich z.B. ermitteln wenn sich die Maus in einem Kreis befindet? Derzeit lege ich ein Rechteck außen herum, aber das ist je nach Form sehr ungenau.

Kreis ist einfach, da musst du nur prüfen, ob die Distanz zum Mittelpunkt kleiner dem Radius ist:
Delphi-Quellcode:
PtInCirle := sqr(X-P.X)+sqr(Y-P.Y) < sqr(R)
, wobei X und Y die Koordinaten z.B. des Cursors sind, P.X und P.Y der Mittelpunkt des Kreises und R der Radius. Die Gleichung wurde hier quadriert um die Wurzel zu sparen.

Für Polygone gibt es auch Verfahren (engl.), aber wenn es wirklich nur um einen Kreis geht, macht es absolut keinen Sinn, das zu implementieren. Ansonsten gibt es natürlich noch die von DeddyH verwendete API-Funktion, aber manchmal will man nicht mit Windows-Regions arbeiten...

[edit]

Hab mal meinen Port für den verlinten Algorithmus ausgegraben:
Delphi-Quellcode:
function TMapRegion.PointInRegion(APoint: TPoint): boolean;
var
  Pt1, Pt2: TPoint;
  PtOld, PtNew: TPoint;
  i: integer;
begin
  if (APoint.X >= FVertexList.BoundingBox.Left) and
     (APoint.X <= FVertexList.BoundingBox.Right) and
     (APoint.Y >= FVertexList.BoundingBox.Top) and
     (APoint.Y <= FVertexList.BoundingBox.Bottom) and
     (FVertexList.Count >= 3) then
  begin
    ///
    /// Ported from http://www.visibone.com/inpoly/
    ///
    Result := False;
    PtOld := FVertexList.Last;
    for i := 0 to FVertexList.Count - 1 do
    begin
      PtNew := FVertexList[i];
      if PtNew.X > PtOld.X then
      begin
        Pt1 := PtOld;
        Pt2 := PtNew;
      end
      else
      begin
        Pt1 := PtNew;
        Pt2 := PtOld;
      end;
      if ((PtNew.X < APoint.X) = (APoint.X <= PtOld.X)) and
         ((APoint.Y-Pt1.Y)*(Pt2.X-Pt1.X) < (Pt2.Y-Pt1.Y)*(APoint.X-Pt1.X)) then
      begin
        Result := not Result;
      end;
      PtOld := PtNew;
    end;
  end
  else
    Result := False;
end;
Bin jetzt zu Faul, den Code für normale Arrays zu verallgemeinern, aber es sollte nicht schwierig sein, ihn entsprechend abzuändern. Relevant ist übrigens nur der Teil innerhalb des if-Konstrukts. In der Abfrage davor wird nur erst mal gegen eine Bounding Box verglichen, was aber lediglich aus Performancegründen geschieht und somit weglassen werden kann.

[/edit]

DeddyH 23. Mär 2012 12:25

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Klar, Regions sind manchmal etwas eklig, aber damit entfallen halt eine Menge eigener Berechnungen. Einfaches Beispiel:
Delphi-Quellcode:
procedure TForm1.FormClick(Sender: TObject);
var
  Rgn: hRGN;
  Pt: TPoint;
begin
  Rgn := CreateEllipticRgnIndirect(ClientRect);
  if Rgn <> 0 then
    try
      Pt := ScreenToClient(Mouse.CursorPos);
      if PtInRegion(Rgn, Pt.X, Pt.Y) then
        ShowMessage('Innerhalb')
      else
        ShowMessage('Außerhalb');
    finally
      DeleteObject(Rgn);
    end;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  Canvas.Brush.Color := clRed;
  Canvas.Pen.Color := Canvas.Brush.Color;
  Canvas.Ellipse(ClientRect);
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  Invalidate;
end;

himitsu 23. Mär 2012 13:30

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Ab XE2 geht auch sowoas, welches man, für D2006/TD bis XE über einen Record-Helper, selber nachrüsten kann.
Delphi-Quellcode:
var
  R: TRect;
  P: TPoint;
begin
  if R.Contains(P) then
    ...
Aber bisher sind dort nur Methoden für rechteckige Dinge integriert.

EWeiss 23. Mär 2012 14:07

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Es gäbe noch die möglichkeit über WindowfromPoint diese methode verwende ich bei PictureBoxen bei meinem Mediaplayer
um zu verhindern das eine function wenn meine Maus sich nicht innerhalb der x,y position befindet nach dem klick
auf einer als Button ausgelegten picBox ausgeführt wird.

Code:
GetCursorPos pos
If WindowFromPoint(pos.X, pos.Y) = PicButton(Index).hWnd Then
Habe jetzt nicht nachgeschaut wie sich das in Delphi verwirklichen läßt.

ups.. hab mich da wohl verlesen ;)

gruss

Aphton 23. Mär 2012 16:03

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Hier, eine etwas allgemeinere Lösung SAT (Separating Axis Theorem)

Kurze Erläuterung der Funktionsweise (soweit ich das richtig in Erinnerung habe):
Man berechnet zu allen Seiten beider Polygone schrittweise die Normalen, und projeziert alle Punkte auf diese Achse (Normale). Dann berechnet man sich die Min-Max Werte auf der Achse für Polygon 1 & 2. Schneiden sich nun diese beiden Bereiche nicht, so gibt es keine Kollision -> die Überprüfung kann vorzeitig beendet werden. Ansonsten kommt es höchstwahrscheinlich (nicht sicher!) zu einer Kollision.

Bild 1
Bild 2
(bei Bild 2 sieht man, dass der grüne und rote Bereich sich nicht schneiden -> keine Kollision)

Delphi-Quellcode:
type
  TVector = record
    X, Y: Single;
    procedure Assign(const AX, AY: Single);
  end;
  TVectorArray = Array of TVector;
  PPolygon = ^TPolygon;
  TPolygon = TVectorArray;

procedure TVector.Assign(const AX, AY: Single);
begin
  X := AX;
  Y := AY;
end;

function vector(const AX, AY: Single): TVector;
begin
  Result.Assign(AX, AY);
end;

function scalarProduct(const A, B: TVector): Single;
begin
  Result := A.X*B.X + A.Y*B.Y;
end;

procedure normalize(var V: TVector);
var
  size: Single;
begin
  size := SQRT(scalarProduct(V, V));
  if size <> 0 then V.Assign(V.X/size, V.Y/size);
end;

function normal(const V: TVector): TVector;
begin
  Result.Assign(V.Y, -V.X);
end;

function BoundingBoxCollisionCheck(const A1, A2, B1, B2: TVector): Boolean; overload;
{   1
    +---------+
    |         |
    |         |
    +---------+ 2
}
begin
  Result := not((A1.X > B2.X) or (A2.X < B1.X) or
    (A1.Y > B2.Y) or (A2.Y < B1.Y));
end;

function BoundingBoxCollisionCheck(const PolyA, PolyB: TPolygon): Boolean; overload;
var
  boxA1, boxA2,
  boxB1, boxB2: TVector;
  procedure createBoundingBox(const P: TPolygon; var bbox1, bbox2: TVector);
  var
    i: Integer;
  begin
    for i := 0 to High(P) do
    begin
      if P[i].X < bbox1.X then
        bbox1.X := P[i].X
      else
      if P[i].X > bbox2.X then
        bbox2.X := P[i].X;
      if P[i].Y < bbox1.Y then
        bbox1.Y := P[i].Y
      else
      if P[i].Y > bbox2.Y then
        bbox2.Y := P[i].Y
    end;
  end;
begin
  with PolyA[0] do
  begin
    boxA1.Assign(X, Y);
    boxA2.Assign(X, Y);
  end;
  with PolyB[0] do
  begin
    boxB1.Assign(X, Y);
    boxB2.Assign(X, Y);
  end;
  createBoundingBox(PolyA, boxA1, boxA2);
  createBoundingBox(PolyB, boxB1, boxB2);
  Result := BoundingBoxCollisionCheck(boxA1, boxA2, boxB1, boxB2);
end;

function SATCollisionCheck(const PolyA, PolyB: TPolygon): Boolean;
var
  P1, P2: PPolygon;
  function collisionCheck: boolean;
  var
    len: Integer;
    i: Integer;
    n: TVector;
    minMaxA, minMaxB: TVector;
    procedure getMinMaxAxisMappedValues(const Polygon: PPolygon; var min, max: Single);
    // axis = n
    var
      j: Integer;
      scalar: Single;
    begin
      min := scalarProduct(Polygon^[0], n);
      max := min;
      for j := 1 to high(Polygon^) do
      begin
        scalar := scalarProduct(Polygon^[j], n);
        if (scalar < min) then
          min := scalar
        else
        if scalar > max then
          max := scalar;
      end;
    end;
    function collision1DCheck(const A, B: TVector): Boolean;
    begin
      Result := not((A.X > B.Y) or (A.Y < B.X));
    end;
  begin
    Result := False;
    // gehe alle Punkte des Polygons 1 durch, bilde die Gerade und berechne darauf die Normale
    len := Length(P1^);
    for i := 0 to len do
    begin
      with P1^[(i+1) mod len] do
        n := normal(vector(X - P1^[i].X, Y - P1^[i].Y)); // n = normal(AB)
      normalize(n);
      // mappe nun alle punkte beider Polygone auf die Achse (punkt * n) und merke dir
      // min max Werte
      getMinMaxAxisMappedValues(P1, minMaxA.X, minMaxA.Y);
      getMinMaxAxisMappedValues(P2, minMaxB.X, minMaxB.Y);
      // wenn beide Bereiche sich NICHT schneiden, so kommts zu keienr Kollision und es kann beendet werden
      if not collision1DCheck(minMaxA, minMaxB) then Exit;
    end;
    Result := True;
  end;
begin
  Result := False;
  if (Length(PolyA) < 2) or (Length(PolyB) < 2) then Exit;
  if not BoundingBoxCollisionCheck(PolyA, PolyB) then Exit;
  P1 := @PolyA;
  P2 := @PolyB;
  if collisionCheck then
  begin
    P1 := @PolyB;
    P2 := @PolyA;
    Result := collisionCheck;
  end;
end;
Das ganze ist noch mit Bounding Boxes optimiert.
Hf

Namenloser 23. Mär 2012 16:07

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Das Separating Axis Theorem gilt aber afair nur für konvexe Polygone.

Aphton 23. Mär 2012 16:12

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ist ja logisch, denn alle Punkte werden ohne weiteres projeziert. Allein von der Logik her ist das ersichtlich.
Daher schrieb ich
Zitat:

Ansonsten kommt es höchstwahrscheinlich (nicht sicher!) zu einer Kollision
Aber danke

Amateurprofi 24. Mär 2012 06:57

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Hab das nur überflogen, aber ich glaube das Thema hatten wir hier schon mal:http://www.delphipraxis.net/598273-post18.html

Root2k 26. Mär 2012 07:05

AW: Überprüfen ob Mausklick in bestimmten bereich war
 
Danke für die vielen hilfreichen Beiträge.
Damit habt Ihr mir schon mal sehr weitergeholfen!


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:50 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz