Hey,
Ik ben momenteel aan het werken aan een vrij simpele 'shape editor', vergelijkbaar met de winforms editor in Visual Studio.
Korte uitleg over wat ik tot nu toe heb:
Ik heb een abstracte klasse Shape, waar elke vorm (denk aan Rectangle, Ellipse, Triangle, etc) van zal overerven. Deze klasse heeft een aantal basis benodigdheden, zoals een Bounds property (alsook Location en Size, wat uit de Bounds gehaald kan worden) die de Rectangle aangeeft waar de shape zich bevind. Er is ook een Move methode en een Resize methode (daarover later meer).
De klasse heeft tenslotte uiteraard een abstracte Draw methode, die elke eigenlijke vorm zelf moet implementeren.
Daarnaast heb ik een klasse Canvas (erft over van Panel), wat in principe het 'tekenbord' is waar alle shapes op verschijnen. Deze klasse heeft een lijstje (List(Of T)) van shapes, en een SelectedShape property, die de huidig geselecteerde Shape bevat (op het moment is er geen mogelijkheid om meerdere shapes tegelijk te selecteren).
In de OnPaint methode van de Canvas wordt met een simpel loopje door de shapes List van elke shape de Draw methode aangeroepen. Naast de Draw methode wordt ook de DrawDragHandles methode aangeroepen, die een selectie + vierkantjes in de hoeken tekent, net als in de form designer van Visual Studio.
Om de shapes te kunnen manipuleren gebruik ik de verscheidene OnMouse____ overrides van de Canvas klasse. Bij OnMouseDown bijvoorbeeld wordt gekeken of de muisklik in een van de 'drag handles' is (of aan de zijkant), wat dus tot resizen moet leiden, of ergens in het midden, wat dus voor een move moet zorgen. Om dit onderscheid te maken is er een enum HitStatus, met waarden als None (buiten de shape geklikt), Drag, ResizeLeft, ResizeTopLeft, Resize___ etc, voor elke richting.
Als de muis ingedrukt wordt, dan wordt een van deze keuzes in een globale 'hitStatus' variabele opgeslagen, zodat ik in de OnMouseMove kan kijken wat ik moet doen (verplaatsen of resizen).
In de OnMouseMove gebeurt dus dit:
De Move methode is simpel: de Bounds krijgt gewoon een offset.
De Resize methode is ook niet super ingewikkeld, maar wel wat lang. Ik moet natuurlijk voor elke richting apart de Bounds aanpassen. Dat doe ik dus zo:
Dit werkt allemaal prima. Ik kan de shapes verplaatsen en resizen zonder problemen.
Maar nu wil ik graag de mogelijkheid hebben om de shapes een MinimumSize te geven, waar ze dus niet onder mogen komen. Ik heb alleen geen idee hoe ik dit correct moet implementeren...
Mijn eerste, "naive", oplossing was een simpele check op het eind van de Resize methode: als de Height groter is dan de minimum Height, dan zet ik de newBounds.Height terug naar de minimum height (en hetzelfde voor de Width). Dit kan ik natuurlijk makkelijker schrijven met de Math.Max functie:
Hoewel dit in principe werkt (de shape wordt nooit kleiner dan het minimum), zijn er twee problemen:
1. Wanneer je de shape tot de minimum grootte resized, en dan toch met je muis door blijft bewegen, dan gebeurt er iets raars als je de muis daarna weer terug beweegt (met de knop nog steeds ingedrukt). De shape begint meteen weer met resizen, ook al is de muis nog lang niet over de 'grens' heen.
2. Wanneer je, bijvoorbeeld, de shape van de linkerkant kleiner maakt, dan begint de shape te verplaatsen (naar rechts) zodra je het minimum bereikt.
Ik snap dat dit waarschijnlijk onbegrijpelijk is, dus vandaar een kort filmpje met het probleem:
http://www.youtube.com/watch?v=gPg8ZK2KJI8
Eerst zie je probleem 1 (de shape wordt weer groter terwijl de muis nog ergens links-boven de shape zweeft), en daarna probleem 2.
Wat probleem 1 veroorzaakt snap ik nog niet helemaal...
Voor probleem 2 denk ik dat dit te maken heeft met mijn, te naive, implementatie van de minimum size. Als er van links geresized wordt bijvoorbeeld, dan verander ik de newBounds op deze manier:
Ik maak dus niet alleen de breedte kleiner, maar ik verplaats de shape ook naar rechts (door de X coordinaat dx groter te maken). Als ik dat niet doe dan resized de shape natuurlijk gewoon van rechts, omdat een Rectangle nu eenmaal vanuit de linker-bovenhoek gedefinieerd is.
Maar als de breedte nu te klein wordt, dan wordt die gewoon gereset. De verplaatsing (X + dx) wordt echter niet meer terug gezet, dus verplaatst de shape wel...
Dit heb ik geprobeerd op te lossen door de check op het einde zo te doen:
Dit werkt al ietsje beter, maar nog steeds niet optimaal. Nu zie je de shape soms wat verspringen, geen idee waarom...
Heeft iemand enig idee hoe ik een minimum size correct implementeer? Is mijn hele resize implementatie 'te makkelijk', is er een betere manier?
Hier moet toch wel iets over bekend zijn, aangezien er zoveel programma's zijn die zoiets gebruiken (denk aan alle Office producten, Visual Studio, etc...) en daar werkt het wel allemaal prima.
Bedankt!
Ik ben momenteel aan het werken aan een vrij simpele 'shape editor', vergelijkbaar met de winforms editor in Visual Studio.
Korte uitleg over wat ik tot nu toe heb:
Ik heb een abstracte klasse Shape, waar elke vorm (denk aan Rectangle, Ellipse, Triangle, etc) van zal overerven. Deze klasse heeft een aantal basis benodigdheden, zoals een Bounds property (alsook Location en Size, wat uit de Bounds gehaald kan worden) die de Rectangle aangeeft waar de shape zich bevind. Er is ook een Move methode en een Resize methode (daarover later meer).
De klasse heeft tenslotte uiteraard een abstracte Draw methode, die elke eigenlijke vorm zelf moet implementeren.
Daarnaast heb ik een klasse Canvas (erft over van Panel), wat in principe het 'tekenbord' is waar alle shapes op verschijnen. Deze klasse heeft een lijstje (List(Of T)) van shapes, en een SelectedShape property, die de huidig geselecteerde Shape bevat (op het moment is er geen mogelijkheid om meerdere shapes tegelijk te selecteren).
In de OnPaint methode van de Canvas wordt met een simpel loopje door de shapes List van elke shape de Draw methode aangeroepen. Naast de Draw methode wordt ook de DrawDragHandles methode aangeroepen, die een selectie + vierkantjes in de hoeken tekent, net als in de form designer van Visual Studio.
Om de shapes te kunnen manipuleren gebruik ik de verscheidene OnMouse____ overrides van de Canvas klasse. Bij OnMouseDown bijvoorbeeld wordt gekeken of de muisklik in een van de 'drag handles' is (of aan de zijkant), wat dus tot resizen moet leiden, of ergens in het midden, wat dus voor een move moet zorgen. Om dit onderscheid te maken is er een enum HitStatus, met waarden als None (buiten de shape geklikt), Drag, ResizeLeft, ResizeTopLeft, Resize___ etc, voor elke richting.
Als de muis ingedrukt wordt, dan wordt een van deze keuzes in een globale 'hitStatus' variabele opgeslagen, zodat ik in de OnMouseMove kan kijken wat ik moet doen (verplaatsen of resizen).
In de OnMouseMove gebeurt dus dit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) MyBase.OnMouseMove(e) If mouseDown Then If Me.SelectedShape IsNot Nothing Then If hitStatus = Shape.HitStatus.Drag Then 'we moeten verplaatsen Me.SelectedShape.Move(e.X - moveStart.X, e.Y - moveStart.Y) moveStart = e.Location ElseIf hitStatus <> Shape.HitStatus.None Then 'we moeten resizen Me.SelectedShape.Resize(hitStatus, e.X - resizeStart.X, e.Y - resizeStart.Y) resizeStart = e.Location End If End If End If End Sub |
De Move methode is simpel: de Bounds krijgt gewoon een offset.
De Resize methode is ook niet super ingewikkeld, maar wel wat lang. Ik moet natuurlijk voor elke richting apart de Bounds aanpassen. Dat doe ik dus zo:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Public Sub Resize(ByVal hitStatus As HitStatus, ByVal dx As Integer, ByVal dy As Integer) Dim newBounds As Rectangle = Me.Bounds Select Case hitStatus Case HitStatus.TopLeft newBounds = New Rectangle(newBounds.X + dx, newBounds.Y + dy, newBounds.Width - dx, newBounds.Height - dy) Case HitStatus.Bottom newBounds = New Rectangle(newBounds.X, newBounds.Y, newBounds.Width, newBounds.Height + dy) 'etc, voor elke mogelijke richting End Select Me.Bounds = newBounds End Sub |
Dit werkt allemaal prima. Ik kan de shapes verplaatsen en resizen zonder problemen.
Maar nu wil ik graag de mogelijkheid hebben om de shapes een MinimumSize te geven, waar ze dus niet onder mogen komen. Ik heb alleen geen idee hoe ik dit correct moet implementeren...
Mijn eerste, "naive", oplossing was een simpele check op het eind van de Resize methode: als de Height groter is dan de minimum Height, dan zet ik de newBounds.Height terug naar de minimum height (en hetzelfde voor de Width). Dit kan ik natuurlijk makkelijker schrijven met de Math.Max functie:
code:
1
2
3
| newBounds.Height = Math.Max(Me.MinimumSize.Height, newBounds.Height) newBounds.Width = Math.Max(Me.MinimumSize.Width, newBounds.Width) Me.Bounds = newBounds |
Hoewel dit in principe werkt (de shape wordt nooit kleiner dan het minimum), zijn er twee problemen:
1. Wanneer je de shape tot de minimum grootte resized, en dan toch met je muis door blijft bewegen, dan gebeurt er iets raars als je de muis daarna weer terug beweegt (met de knop nog steeds ingedrukt). De shape begint meteen weer met resizen, ook al is de muis nog lang niet over de 'grens' heen.
2. Wanneer je, bijvoorbeeld, de shape van de linkerkant kleiner maakt, dan begint de shape te verplaatsen (naar rechts) zodra je het minimum bereikt.
Ik snap dat dit waarschijnlijk onbegrijpelijk is, dus vandaar een kort filmpje met het probleem:
http://www.youtube.com/watch?v=gPg8ZK2KJI8
Eerst zie je probleem 1 (de shape wordt weer groter terwijl de muis nog ergens links-boven de shape zweeft), en daarna probleem 2.
Wat probleem 1 veroorzaakt snap ik nog niet helemaal...
Voor probleem 2 denk ik dat dit te maken heeft met mijn, te naive, implementatie van de minimum size. Als er van links geresized wordt bijvoorbeeld, dan verander ik de newBounds op deze manier:
code:
1
2
3
4
| newBounds = New Rectangle(newBounds.X + dx, newBounds.Y, newBounds.Width - dx, newBounds.Height) |
Ik maak dus niet alleen de breedte kleiner, maar ik verplaats de shape ook naar rechts (door de X coordinaat dx groter te maken). Als ik dat niet doe dan resized de shape natuurlijk gewoon van rechts, omdat een Rectangle nu eenmaal vanuit de linker-bovenhoek gedefinieerd is.
Maar als de breedte nu te klein wordt, dan wordt die gewoon gereset. De verplaatsing (X + dx) wordt echter niet meer terug gezet, dus verplaatst de shape wel...
Dit heb ik geprobeerd op te lossen door de check op het einde zo te doen:
code:
1
2
3
4
5
6
7
8
| If newBounds.Height < Me.MinimumSize.Height Then newBounds.Height = Me.MinimumSize.Height 'reset height newBounds.Y = Me.Bounds.Y 'reset location End If If newBounds.Width < Me.MinimumSize.Width Then newBounds.Width = Me.MinimumSize.Width 'reset height newBounds.X = Me.Bounds.X 'reset location End If |
Dit werkt al ietsje beter, maar nog steeds niet optimaal. Nu zie je de shape soms wat verspringen, geen idee waarom...
Heeft iemand enig idee hoe ik een minimum size correct implementeer? Is mijn hele resize implementatie 'te makkelijk', is er een betere manier?
Hier moet toch wel iets over bekend zijn, aangezien er zoveel programma's zijn die zoiets gebruiken (denk aan alle Office producten, Visual Studio, etc...) en daar werkt het wel allemaal prima.
Bedankt!