Entwickler-Ecke

Multimedia / Grafik - Objekte auf einer Fläche erstellen, welche beweglich sind..


trm - Fr 16.12.11 04:16
Titel: Objekte auf einer Fläche erstellen, welche beweglich sind..
Hallo,

ich möchte gern für ein neues Projekt eine Fläche erstellen, auf welcher ich farbige Objekte erstellen kann, die ich bei Bedarf

* in der Breite/Höhe verändern kann
* an eine andere Stelle auf der Fläche verschieben kann

Wenn ich auf die Fläche mit der Maus klicke, soll eine Art Rahmen gezogen werden. Alternativ reicht ein Klick aus, um den Rahmen zu erstellen. Die Größenänderung ist aber wichtig.


Das ganze wird kein Spiel, eher eine Officeanwendung. Daher benötige ich kein DirectX/OpenGL oder sonst etwas in dieser Richtung.


Ich nutze Delphi7 und möchte weitesgehend ohne fremde Komponenten auskommen.

Hat jemand schonmal sowas gemacht und kann mir Tipps geben?


bummi - Fr 16.12.11 09:28

Eine Paintbox, ein paar Mausauswertungen und ein Array oder eine Liste mit Objekten/Records welche die Beschreibung (Position, Größe, Form, Farbe etc ...) der Elemente aufnehmen, in Paint der Paintbox wird hieraus dargestellt.


ub60 - Fr 16.12.11 21:29

Zumindest für das Verschieben hätte ich etwas: http://www.delphi-treff.de/tutorials/vcl/tlist/einleitung/.
Die Größenänderung sollte sich da auch reinbasteln lassen.

ub60


Xion - Fr 16.12.11 21:59

Wenns ums verschieben geht kannst du mal das hier angucken:
http://www.delphi-forum.de/viewtopic.php?p=653296#653296

Größenveränderung kann man da auch noch einbauen. (Achtung: in dem Code ist ein kleiner Fehler, da das Raster sich verschiebt, wenn man das Fenster verschiebt ;) )


Delete - Sa 17.12.11 00:37

Hi,

ich hab hier die letzte OpenSource-Version von ImageEn auf meinem Server, mit der das relativ einfach gehen sollte:
http://yorako1.bplaced.net/delphicomps/iesrc_312.zip


Delete - Sa 17.12.11 00:42

in der unit ieEnview.pas und-ioutil.pas ist das sehr gut beschrieben... du kannst da ableiten und "lernen", um die units nicht zu "usen"


trm - Sa 17.12.11 01:23

Danke für die Hilfen,

ub60 das sieht gut aus. Warum Pointer verwendet werden, weiss ich leider nicht, aber so in etwa würde ich das gern umsetzen.


Xion, ein Panel zu verschieben geht noch weitaus einfacher, dafür benötigt man keine 3 verschiedene Ereignisse :)


Edit @rd3, das schau ich mir mal an :)

Edit2: @rd3: Die beiden von Dir genannten units sind im zip-Archiv nicht drin.


Delete - Sa 17.12.11 02:25

das hab ich jetzt aus dem kopf gesagt... es sind ne ganze menge units... ich hab jetzt keinen compiler hier... es war nur aus erinnerung... imageenview... imageio... die heißen doch alle so ähnlich...


bummi - Sa 17.12.11 11:52

Ich habe mal einen Microrumpf für so etwas für Dich aufgesetzt, vielleicht willst Du es ausbauen.
Shift + MouseDown = neues Objekt
Strg + MouseMove = Grösse ändern
Alt + MoseMove = verschieben

hier könnte man dann noch Einbauen Typ ändern, Farbe ändern etc.


trm - Sa 17.12.11 16:14

Hallo nochmal,

ich hab das hier doch wieder auf gemacht, weil da noch Fragen von meiner Seite aus bestehen ;)


Im Anhang ist ein Test von dem, was es später werden soll.

Links im 2. Panel bitte mal drauf drücken, dann in der rechten Paintbox eine Linie ziehen.

Komischerweise stimmen die TOP-Maße nicht mit den Panels überein - und ich sehe nicht, wo der Fehler ist.
Beim Panel 4 und 5 geht es, ab Panel 6 stimmt gar nix mehr und 1-3 sind verkehrt.


ub60 - Sa 17.12.11 17:12

Die Bezeichnung der Panels ist gruselig :roll: (Panel45, Panel51, Panel43, Panel52, ...), das nur so nebenbei. Da macht eine Fehlersuche keinen Spaß.
Davon abgesehen kannst Du den y-Wert des Rechtecks sehr einfach bestimmen. Angenommen, Deine Panelhöhe ist 41, rechnest Du X (von der Maus) div 41 und dann das Ganze mal 41. Damit erhältst Du immer den oberen Rand der Panel. Addiere dann zum Beispiel noch 5, dass sich die einzelnen Belegungsrechtecke nicht berühren.

Zur Bemerkung mit den Pointern in dem Tutorial: Die sind dort nicht sehr sinnvoll, man sollte dies besser mit einer TObjectList machen. Da kann man dann auch ganz gut löschen.

ub60


bummi - So 18.12.11 00:27

so geht es ...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  Dummy_Integer: Integer;
  a, b : Integer;
  Dummy_Panel: TPanel;
begin

  Dummy_Bool_MouseDown := (ssLeft in Shift);

  if Dummy_Bool_MouseDown then
  begin
    if Y <= (Panel_OverView_Belegung_ZeitLeiste.Top + Panel_OverView_Belegung_ZeitLeiste.Height) then
      Y := Panel45.Top + 1;

    if Y > (Panel_OverView_Belegung_ZeitLeiste.Top + Panel_OverView_Belegung_ZeitLeiste.Height) then
    begin
      for Dummy_Integer := 0 to Panel29.ControlCount - 1 do
        if Panel29.Controls[Dummy_Integer] is TPanel then
        begin
          Dummy_Panel := TPanel(Panel29.Controls[Dummy_Integer]);
          a := Dummy_Panel.Top;
          b := Dummy_Panel.Top + Dummy_Panel.Height;

          if (Y >= a) and (Y <= b) then
          begin
            Dummy_Panel.Caption := IntToStr(Y) + ': ' + IntToStr(a) + #32 + IntToStr(b);
            Y := a + 1;
            Break;
          end;
        end;
    end;

    X_Start := X;
    Y_Start := Y;
  end;

end;


trm - So 18.12.11 04:41

user profile iconub60 hat folgendes geschrieben Zum zitierten Posting springen:
Die Bezeichnung der Panels ist gruselig :roll: (Panel45, Panel51, Panel43, Panel52, ...), das nur so nebenbei.


Danke ;) Nur so nebenbei.. es sind Testobjekte. In einem fertigen Programm werden diese Flächen dynamisch erstellt.


user profile iconbummi hat folgendes geschrieben Zum zitierten Posting springen:
so geht es ...


Danke user profile iconbummi, verstehen kann ich es leider dennoch nicht. Wenn eine Variable in einer Menge erwartet wird und diese Erwartung nicht erfüllt wird, springt Delphi dennoch auf den Zug auf (Y=100; a=300; und c=350;) :/

Schönen Sonntag an euch.


bummi - So 18.12.11 09:41

Ich verstehe nicht was Du meinst ... solange Du im Bereich der Panels bist passt es, außerhalb kommt derzeit noch Y, wenn es das ist, das ließe sich so umgehen

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  var
  Dummy_Integer: Integer;
  a, b,yy : Integer;
  Dummy_Panel: TPanel;
begin

  Dummy_Bool_MouseDown := (ssLeft in Shift);
  yy := -1;
  if Dummy_Bool_MouseDown then
  begin
    if Y <= (Panel_OverView_Belegung_ZeitLeiste.Top + Panel_OverView_Belegung_ZeitLeiste.Height) then
      Y := Panel45.Top + 1;

    if Y > (Panel_OverView_Belegung_ZeitLeiste.Top + Panel_OverView_Belegung_ZeitLeiste.Height) then
    begin
      for Dummy_Integer := 0 to Panel29.ControlCount - 1 do
        if Panel29.Controls[Dummy_Integer] is TPanel then
        begin
          Dummy_Panel := TPanel(Panel29.Controls[Dummy_Integer]);
          a := Dummy_Panel.Top;
          b := Dummy_Panel.Top + Dummy_Panel.Height;

          if (Y >= a) and (Y <= b) then
          begin
            Dummy_Panel.Caption := IntToStr(Y) + ': ' + IntToStr(a) + #32 + IntToStr(b);
            yy := a + 1;
            Break;
          end;
        end;
    end;

    X_Start := X;
    Y_Start := YY;
  end;

end;



und ein entsprechedes

if Y_Start = -1 then exit;

in den anderen Routinen.


delfiphan - So 18.12.11 11:46

Ich hätte das jetzt nicht unbedingt mit Komponenten gemacht (sprich Panels). Das geht grundsätzlich schon auch, aber wenn es dann darum geht, Linien und Pfeile zu zeichnen ist's nicht ganz das richtige. Ausserdem performen die Dinger nicht sehr gut.

Das MicroPaint funktioniert zwar, ich finde es aber ziemlich gefährlich so zu beginnen. Denn das ganze ist architektonisch auf wackeligen Füssen und kaum erweiterbar - daraus wird irgendwann mal ein monolithisches Chaos, wenn man daran einige Wochen und Monate weiterentwickelt.

Ich hätte jetzt das ganze sauber nach "Schulbuch-OOP" gemacht und die verschiedenen Concerns getrennt. Irgend sowas wie:

- Modellklassen: OvalShape, RectangleShape, LineShape, ArrowShape. Keine GUI sondern reine Datenklassen mit ein paar Properties ohne Logik. Ein zentrales Updated-Event würde ich jedoch noch machen.

- Ansichtsmodellklassen: Diese wrappen jeweils ein Modell und bieten zusätzlich weitere GUI-spezifische Dinge wie "Griffe" zum Vergrössern und Verkleinern an den Ecken an. Verschiedene Shapes können ja verschiedene Griffe haben: Ein OvalShape hat vielleicht 4 Griffe (oben, unten, rechts, links), ein RectangleShape 8 (zusätzlich noch in den Ecken), eine Linie hat nur 2 Griffe (an den beiden Enden). Diese Klassen sind ebenfalls reine Datenklassen. D.h. für die Griffe habe ich z.B. ein Property, welches eine Liste von HandleShapes oder so ist. Es muss nicht zwingend pro Modellklasse eine Ansichtsklasse existieren.

- Ansichtsklassen: Diese rendern das Ansichtsmodell und benachrichtigen das Ansichtsmodell, wenn irgendwas geklickt wurde. Die Ansichtsklasse implementiert zudem Hittests, usw.. Die Ansichtsklassen sind zustandslos.

Schlussendlich braucht man vielleicht noch ein dünnes Framework, welches die zusammengehörenden Klassen miteinander in Verbindung bringt. Die Assoziationen könnte man über eine Konfiguration oder In-Code durch Attribute steuern. Man könnte da auch ein bisschen etwas hartcodieren.

Das ganze gibt am Tag 1 eine etwas längere Nacht. Danach hast du aber eine saubere Sache. Einige Vorteile:
- Die Ansichtsklassen enthalten keine Zustandsinfos - diese sind im Ansichtsmodell. Die Ansichtsklassen kann man daher jederzeit austauschen, theoretisch sogar zur Laufzeit (z.B. Wechsel zwischen OpenGL und Canvas).
- Das Ansichtsmodell enthält das allermeiste an Logik. Diese Logik ist aber klar vom Modell getrennt. Im Modell sind nur noch Daten. Diese können einfach persistiert werden.
- Erweiterbar. Kommt morgen das Requirement, dass ich Sachen gruppieren können muss, mache ich ein neues Modell, das aus einer Liste von Modellen besteht. Als Ansichtsmodell nehme ich vielleicht das fürs Rectangle als Basis und die Ansicht aggregiert entsprechend die Paint-Methoden, Hittests, der Ansichten.

PS: Das ist jetzt einfach mal so aus dem Brauch raus - natürlich müsste man sich das mal genauer anschauen, mal paar Diagramme zeichnen, ein Proof-Of-Concept machen.


trm - So 18.12.11 14:23

Hallo,

ich weiß nicht, ob ihr, die hier antworten, verstanden habt, um was es ging :(
(Bei einigen meine ich, dass die sich den Quelltext vielleicht angeschaut haben aber nicht tief genug. Andere ignorieren meine Fragen.)

user profile iconbummi hat folgendes geschrieben Zum zitierten Posting springen:
Ich verstehe nicht was Du meinst ... solange Du im Bereich der Panels bist passt es [..]


Ich wollte eigentlich wissen, warum der Vergleich mittels

Delphi-Quelltext
1:
if (Y >= a) and (Y <= b) then                    

funktioiert, abder der Vergleich mittels

Delphi-Quelltext
1:
if Y in [a..b] then                    

nicht. Beides sollte logisch gesehen doch vollkommen in Ordnung sein.

Weiterhin fange ich ein "außerhalb der Panel" dann in den folgenden Programmierprozessen anders ab.


user profile icondelfiphan hat folgendes geschrieben Zum zitierten Posting springen:
Ich hätte das jetzt nicht unbedingt mit Komponenten gemacht (sprich Panels).[..]

Danke für Deine Ausführungen, aber das benötige ich wirklich nicht. Auch später nicht. Und die Panels sind immer noch Testkomponenten, ob die später als Panels bestand haben werden, weiß ich noch nicht. Jedenfalls mache ich ja auf den Panels nichts weiter als einen Text auszugeben. Ansonsten dienen die im Moment als Positionsmarker.


Danke an euch. Das Thema ist erledigt, wenn jedoch jemand nur kurz zur Problematik mit dem Vergleich einen Tipp hat, schreibt den doch bitte noch hinzu :)

Schönen 4. Advent.


ub60 - So 18.12.11 15:01

Ich vermute mal, das Problem der ungleichen Behandlung liegt daran:
"Die Ordinalpositionen der Werte müssen zwischen 0 und 255 liegen." (Aus Delphi-Hilfe)
Deine y-Werte werden wohl >255 sein.

Abgesehen davon, so (siehe Source) geht es wirklich einfacher. Mit X_Alt lösche ich übrigens das alte Rechteck vor einer Zeichenoperation in MouseMove, dann flackert es nicht mehr so, da ich mir das Repaint erspare.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
if Dummy_Bool_MouseDown then
  begin
    y:=(y div 41)*41+5;
    X_Start := X;
    Y_Start := Y;
    X_Alt:=X;
  end;

ub60


bummi - So 18.12.11 16:15

Du hast ja dicht gemacht, aber bzgl. der Vergleiche

das hier wird gar nicht kompiliert

if Y in [500..600] then

ich denke es ist davon auszugehen dass die ordinale des in ausgewertet werden, da wo sie sich mit dem Gültigkeitsbereich überlappen kommen "richtige" Ergebnisse.


trm - So 18.12.11 22:30

Danke. Das hatte ich vergessen.