Futás időben hozok létre gombokat egy panel-en és később azokat tetszőlegesen át lehet rendezni és a gombok között 2 féle méretű van. Hogyan lehetne azt megcsinálni, hogy a gombok ne takarhassák be egymást? (Azaz ne lehessen egyik gombot a másikra ráhúzni, valamint ne is tudjon mondjuk 2 pixelnél közelebb kerülni a másikhoz.)
Szerintem azt hagynod kellene hogy ráhúzhassa, mert ha olyan területre akarják húzni a gombot, ami előtt egy rakás másik gomb van, akkor ki kellene kerülnie valamerre az egészet, nem tudja felettük elhúzni. Inkább akkor nézd meg, hogy hol van az áthúzott gomb, amikor elengedik. Ha takarja valami, akkor ahhoz képest igazítod valahova. Persze, akkor újra meg kell vizsgálnod, hogy nehogy most meg egy másik gomb takarja. Vagy eleve egy olyan területet keresel, ahol nincs semmi. A panel gombjait egy for ciklussal be tudod járni.
for i := 0 to panel.ControlCount - 1 do begin if panel.Controls[i] is TButton then // ha gomb begin // ide jöhet a kód, amivel megvizsgálod, hogy takarják-e egymást end; end;
Le kell programozni.Elteszed a gombok paramétereit egy tömbbe.
Minden gomb helyét tudod:
Left,Top
Minden gomb szélességét, magasságát tudod:
Width, Height.
Képzelj el egy mozaikot. Ennek megfelelően nem lehetnek a gombok takarásban. Áthúzni felette miért nem lehet? Én arra gondoltam, hogy amikor elengedi a gombot, akkor vizsgálom hogy van-e ott gomb és ha van, akkor arrébb tolom 1 pixelt és megint megnézem.
Ettől tartok én is. Van ötleted hogyan tudom megcsinálni?
Mert az oké, hogy meg tudom nézni hogy x,y pontban van-e valami, de ha pl. x+1,y pontban van, akkor mi van? Vagy csináljak egy tömböt amiben a panel minden létező x,y koordinátája megvan és abban jelzem a lefoglalt koordinátákat, majd azt vizsgálom hogy a komponensem koordinátái szerepelnek-e az adott koordináták között? De hogyan?
Úgy értettem, hogy miközben húzza, ne vizsgáld, mert akkor nem fogja tudni áthúzni egy másik gomb felett. Amikor elengedi, akkor lehet vizsgálni. Ha ilyen mozaikszerűen akarod (ezt hittem, bárhol lehetnek), akkor miért nem hozol létre egy tömböt, ami az egyes négyzetekben lévő gombokat tárolják, meg a terület koordinátáit? Elengedéskor csak meg kell keresni a tömbben a koordinátáknak megfelelő elemet, és megnézni, hogy van-e benne valami. Ha igen, akkor keresel a közelében egy üreset, és berakod oda.
Létrehozol egy rekord típust. Ebben eltárolod a mozaik egy elemének az adatait, mint Left, Top, Width, Height (talán Right és Bottom célszerűbb lenne). Plusz eltárolod benne azt a gombot, amelyik épp benne van. Ha nincs benne semmi, akkor ennek az értéke nil. Készítesz egy tömböt, ami annyi elemű, amennyi elemet akarsz a mozaikban. Utána az elemeknek megadod, hogy a képernyőn melyik területhez tartoznak. Ha egy gombot húznak valahova, akkor elkezded végigjárni a tömböt, és megnézed, hogy melyik elem területén engedték el. Ha ott a gomb = nil, akkor mehet oda. Értelemszerűen tudni kell, hogy honnan húzták a gombot, mert ott nil-re kell állítani a gombot.
Használj ToolBar-t, az megoldja a problémád. Kivéve azt, hogy azon minden gomb egyforma nagyságú. Állítsd True-ra a Customizable property-jét, a gomboknak meg van egy Marked property-ja, amivel tudod jelölni, ha kiválasztanak egyet. Húzogatni sem kell a gombokat, mert ha kétszer rákattint a user, akkor úgy állítja magának a sorrendet, ahogy akarja.
Ez eddig oké, de mi van ha csak a gomb 1/3-ig húzza? pl. egy 3x3-as pixelű gomb esetén 9 pontból áll a gomb. Vegyünk egy 6x6-os hátteret (rácsot) amin lehet toligálni. Ekkor 4x4=16 féle módon lehet elhelyezni ezen a rácson. Értelem szerűen minél kisebb a gomb és nagyobb a rács a lehetőségek száma úgy nő. És akkor nekem minden egyes lehetőséget fel kellene töltenem egy tömbbe és az vizsgálni?? 542x809-as jelenleg a rácsom amint tetszőleges számú gomb helyezhető el mondjuk és tetszőleges arányban a kicsi és nagy gombok.
Ha 6x6-os a rács, akkor egy 36 elemű tömb kell. Ha egy terület 5x5 pixeles, akkor a tömb első elemében ez lesz: Left = 1, Top = 1, Bottom = 6, Right = 6, Gomb = nil. Ha az elengedéskor mondjuk a gomb bal felső sarka ezen a területen belül van, akkor mehet oda. gomb.Left := elem.Left és gomb.Top := elem.Top. Vagy ehhez még hozzáadsz 1 pixelt, ha akarsz egy kis helyet kihagyni. Ezért írtam, hogy 5x5-ös a terület. Lesz körbe egy pixel.
Ne vicceljetek már azokkal a tömbökkel! Itt egy példa. Minden gombnak létrehozol egy a területének megfelelő region-t, majd amikor a gombot mozgatod, a RectInRegion api-val ellenőrzöd, hogy fedi-e valamelyiket. Ebből a kódból kiindulhatsz. Tegyél egy gombot a formra és annak implementáld a MouseMove és MouseDown eseményét, úgy ahogy itt van:
... type TForm1 = class(TForm) ...
const PIC_NUM = 5;
var Form1: TForm1; Rgns: array[0..PIC_NUM-1] of HRGN; fx: Integer; fy: Integer; BrActive: HBRUSH; BrInactive: HBRUSH;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject); var i: Integer; begin //a Rgns minden elemet es a ket brush objektumot fel kell szabaditani az //OnCloseQueryben a DeleteObject() apival! Rgns[0] := CreateRectRgn( 0, 0, Width, Height ); Rgns[1] := CreateRectRgn( 50, 50, 200, 300 ); Rgns[2] := CreateRectRgn( 210, 250, 300, 350 ); Rgns[3] := CreateRectRgn( 300, 100, 500, 200 ); Rgns[4] := CreateRectRgn( 350, 250, 400, 400 ); BrActive := CreateSolidBrush( RGB( 255, 0, 0 ) ); BrInactive := CreateSolidBrush( RGB( 200, 200, 200 ) );
for i := 1 to pic_num - 1 do CombineRgn( Rgns[0], Rgns[0], Rgns[i], RGN_XOR ); end;
procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var rt: TRect; i: Integer; begin if ssLeft in Shift then begin Button1.Left := Button1.Left + x - fx; Button1.Top := Button1.Top + y - fy; GetWindowRect( button1.Handle, rt ); Windows.ScreenToClient( handle, rt.TopLeft ); Windows.ScreenToClient( handle, rt.BottomRight );
for i := 1 to PIC_NUM -1 do begin if RectInRegion( Rgns[i], rt ) then // hoppsz, fedes van! FillRgn( Form1.Canvas.Handle, Rgns[i], BrActive ) else FillRgn( Form1.Canvas.Handle, Rgns[i], BrInactive ); end; end; end;
procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin fx := x; fy := y; end;
Fogd a gombot és sétáltasd a formon!
Tehát neked létre kell hozni minden egyes gombodnak egy regiont(lásd CreateRectRgn) amelyeknek megadod egyenként a gombok koordinátáit. Példának ott a FormCreate esemény.
Ha a gombod egyetlen pixele is fedi valamelyik másikat, akkor rögtön látni fogod.
Ez a részt pl hogy is kell értelmezni? Az Rgns[0] az pl a button1 koordinátáit kell tartalmazza? Az első a top, a második a left a harmadik a width és a negyedik a height értéke? Én ezt a részt a panel1 onClick eseményéhez szeretném rendelni. Úgy is megy?
Ez a rész festegeti a négyszöget az RGB()-ben megadott színre. De nekem nem festenie kellene - sőt ne is fesse! - hanem az hogy annyi pixelt ugorjon el hogy ne érjen hozzá a már ott lévő komponenshez. Ez is megoldható valahogy? Vagy legalább ne engedje lerakni oda, hanem ugorjon akkor inkább vissza a kiindulási koordinátákra.
Köszi, a formcreate-s részt nem értem.
Most magyarázzam én el? Írd be a google-be CreateRectRgn és amit a microsoft oldalán látsz találatot, olvasd el.
Az Rgns[0] az pl a button1 koordinátáit kell tartalmazza?
Nem, ki mondta ezt? Width, Height semmilyen minősítés nélkül, kire vonatkozik?
De nekem nem festenie kellene - sőt ne is fesse! Akkor ne fesd. De te nem látod, hogy ez egy szemléltető példa arra, hogy milyen egyszerű megnézni, hogy egy téglalap ott van-e ahol kellene legyen. Ha ebből nem érted mit kellene kivenned, akkor hagyd abba az egészet.
De nem értem, hogy honnan tudom a régió koordinátáit meg. Mert ugye delphiben tudom az ablak koordinátáit, a rajta lévő objektumok koordinátáit (pl. panel), de mit kell a régiónak ilyenkor megadni? A pl. panel koordinátáit vagy az egész windows asztal koordinátáiból az adott objektumra eső koordinátákat, vagy az adott form koordinátiból az adott objektum koordinátáit vagy esetleg csak az objektumét? (Utóbbi nem valószínű mert akkor nem lehetne egyedi formokat gyártani.)
Valamint nem értem hogy a GetWindowRgn-ből hogyan kapom meg a koordinátákat - mert sejtésem szerint nekem ez kell majd.
Senki. Csak kérdezem. Mivel azt gondoltam hogy a width, height annak a komponensnek a mérete amire kattintok. Viszont a leírás szerint először a régiót kell megadni. Csak akkor mit jelent a width és height? Minek a méretei? És miért van 5 CreateRectRgn? Ha ez 5 régió, akkor miért pont 5? Én úgy értettem, hogy kell 1 régió amin "mászkálok" - tehát a keret amiben rajzolni akarok - és egy amit bele akarok rajzolni. Több régiót lehet kombinálni és így érdekes alakzatokat létrehozni. Vagy nem így van?
Arra jutottam, hogy azt kell figyelnem a GetWindowRgn-vel, hogy van-e átfedés. Viszont ezt melyik értéke adja meg? Jól értem, hogy a régiók jobb alsó sarkának átfedését figyeli ez? De honnan tudja melyiket kell figyelje? A sorrendből? Mert hogy ha van egy nagy régióm és egy kicsi mozog rajta, akkor mindig azt fogja mondani hogy átfedés van. Én meg futás időben hozom létre a pl. gombokat. Honnan tudom meg a handle-jét a gombnak? Ill. Honnan tudom, hogy melyik régió melyik gombhoz tartozik? Azaz hogy éppen melyik gombot mozgatom a sok közül? (Pl. hogy melyik gombot tolom rá éppen a másikra, hiszen az álló gombot nem akarom eltolni.) És végül amit nem látok még ebből, hogy honnan tudom meg merre kell és hány pixelt mozgatni a gombot, hogy "leugorjon" a gombról? (Ha nem azt akarom hogy visszaugorjon a kiindulási pozícióba.)
Ja és még egy valami nem tiszta nekem: az angol leírásban az van, hogy ha befejeztem a régiók csesztetését, akkor meg kell szüntetnem őket. Ezek szerint ha a formot bezárom, vagy lecsukom akkor a régiókat is meg kell szüntetnem v. azt a windows automatikusan megteszi?
var i,a,b: integer; LPanel: TPanel; begin setlength(pontok, panel1.Width*panel1.Height); i:=0; while (i<panel1.Width*panel1.Height) do begin pontok[i]:=0; i:=i+1; end;
i:=0; if Sender is TBitBtn then APanel := TBitBtn(Sender).Parent as TPanel else if Sender is TPanel then APanel := Sender as TPanel;
while i <= Panel1.ComponentCount-1 do begin if (Panel1.Components[i] is Tpanel) then begin LPanel:=TPanel(Panel1.Controls[i]); if (LPanel.Name <> APanel.Name) then begin a:=(LPanel.Top-1)*Panel1.Width+LPanel.Left; while (a < (LPanel.Top+LPanel.Height-2)*Panel1.Width+LPanel.Left) do begin b:=0; while (b < LPanel.Width) do begin pontok[(a+b)]:=1; b:=b+1; end; a:=a+Panel1.Width; end; end; end; i:=i+1; end; end;
Az OnMouseUP-ba:
Var i,a,b: integer; APanel: TPanel; begin if Sender is TBitBtn then APanel:=TBitBtn(Sender).Parent as TPanel else if Sender is TPanel then APanel:=Sender as TPanel; a:=((APanel.Top-1)*Panel1.Width)+(APanel.Left); while (a < (APanel.Top+APanel.Height-2)*Panel1.Width+APanel.Left) do begin b:=0; while (b < APanel.Width) do begin if (pontok[(a+b)] = 1) then begin //ide jön amit csinálni akarok vele majd end; b:=b+1; end; a:=a+Panel1.Width; end; end;
Gyakorlatilag ezt csinálja meg a CreateRectRgn is. Csak itt nem rajzolgat, illetve ha azt akarom akkor én pontonként meg tudom mondani mit is rajzoljon.
Amire viszont még mindig nem jöttem rá, az az, hogy hogyan tudom olyan helyre tolni a közelben ahol van helye a komponensnek. Ehhez most már legalább van egy adathalmazom miben 0-val vannak jelölve azok a pontok amik szabadak. De hogyan tudom kitalálni hogy merre toljam?
Most azon gondolkozom, hogy nem az OnMouseUP-ba hanem az OnMouseMove-ba rakom és mindig eltárolom az előző csúcskoordinátát és ha átfedésbe kerül, akkor áttolom ebbe az előző koordinátába. Lehet ezzel valami gond?
Nos ez is megoldódott. :) Ahogy írtam úgy kell megoldani. Csak ott az onMouseDown-ra el kell tárolni a pozíciót majd azt kell csak figyelni hogy az OnMouseMove-ra belép-e olyan helyre ahol már van letárolva más komponens. És ha igen, akkor a top és left értékeit ezekkel a tárolt értékekkel feltölteni.
És nem árt arra is figyelni, hogy negatívba ne menjen át a pozíció mert az kivételt csinál és elszáll a progi (de a komponens is kezelhetetlenné válik, mivel eltűnik).
Mivel a feladatot végül is magam oldottam meg, így most a pont az enyém.
Viszont még nem pontozom magam, mert kíváncsi vagyok hogy hogyan lehetne ugyan ezt megoldani a régiós módszerrel. Így ha arra is befut egy jó megoldás, akkor a megoldóé a pont.