updated: Simple and useful form layout designer for DBase / SQLite and others
Posted: Fri Jan 09, 2026 8:50 am
Now Upgrade to the new advance version 1.10 :
I use this tools to build DBase IV / SQLite Record View/Edit forms , save the design layout, load the layout position in your database program and read the SQLite table and put in the layout field, it is very convenience . before this , i was hardcode the record fields position by hand .
Feature added :
1. Added selection box for easy multiple object selection
2. Added [Select ALL],[Select Label], [Select Edit] Button for fast selection
3. use proper mouse icon when mouse hover over Selected Objects/ Object Resize Corner
4. Fixed new object placement position , now it will not overlaps or cover other objects when add new
5. smart object placement start from vertical direction first then to next horizontal column if reach canvas bottom
6. Fixed to smooth object movement , no more jerky step
scroll down this page , or here
Updated Version 1.10 :
viewtopic.php?t=88159#p649901
The OLD V1.0 code is still remain here for new people to compare the code and test it , may be you find some use too.
Self explain code , simple modification can do flow chart designer or event other use in your own application , eg. powerpoint is not a dream ...
Example code for learning purpose
just do what you want , don't mention ...
best regards
I use this tools to build DBase IV / SQLite Record View/Edit forms , save the design layout, load the layout position in your database program and read the SQLite table and put in the layout field, it is very convenience . before this , i was hardcode the record fields position by hand .
Feature added :
1. Added selection box for easy multiple object selection
2. Added [Select ALL],[Select Label], [Select Edit] Button for fast selection
3. use proper mouse icon when mouse hover over Selected Objects/ Object Resize Corner
4. Fixed new object placement position , now it will not overlaps or cover other objects when add new
5. smart object placement start from vertical direction first then to next horizontal column if reach canvas bottom
6. Fixed to smooth object movement , no more jerky step
scroll down this page , or here
Updated Version 1.10 :
viewtopic.php?t=88159#p649901
The OLD V1.0 code is still remain here for new people to compare the code and test it , may be you find some use too.
Self explain code , simple modification can do flow chart designer or event other use in your own application , eg. powerpoint is not a dream ...
Example code for learning purpose
just do what you want , don't mention ...
best regards
Code: Select all
; Visual Form Designer in PureBasic 5.7x , 6.1x , 6.2x
; Features: BTN, LABEL, EDIT, PHOTO objects with 5px grid snapping
#Version$ = "1.0 pro"
Structure FormObject
Type.s
X.l
Y.l
Width.l
Height.l
Text.s
ImagePath.s
isSelected.l
SelectionOrder.l
EndStructure
#GRID_SIZE = 5
#MAX_OBJECTS = 100
Global NewList Objects.FormObject()
Global DragMode.i = #False
Global ResizeMode.i = #False
Global ResizeEdge.s = ""
Global DragStartX.l, DragStartY.l
Global DragObjX.l, DragObjY.l
Global DragObjW.l, DragObjH.l
Global OffscreenImage.i
Global SelectionCounter.l = 0
Global PendingClickObj.i = -1
Global PendingCtrlPressed.i = #False
Global HasMoved.i = #False
Global ButtonCounter.l = 0
Global LabelCounter.l = 0
Global EditCounter.l = 0
Global PhotoCounter.l = 0
#RESIZE_HANDLE = 8
Enumeration
#Win_Main
#Canvas_Designer
#Text_Info
#Btn_AddButton
#Btn_AddLabel
#Btn_AddEdit
#Btn_AddPhoto
#Btn_Delete
#Btn_Save
#Btn_SaveClean
#Btn_Load
#Btn_New
#Btn_AlignLeft
#Btn_AlignRight
#Btn_AlignTop
#Btn_AlignBottom
#Btn_DistributeV
#Btn_MatchSize
#Btn_Renumber
#Text_Spacing
#String_Spacing
#LABEL_OBJECT
#LABEL_ALIGNMENT
#LABEL_FILE
EndEnumeration
Procedure SnapToGrid(Value.l)
ProcedureReturn Round(Value / #GRID_SIZE, #PB_Round_Nearest) * #GRID_SIZE
EndProcedure
Procedure IsSelected(ObjIdx.i)
SelectElement(Objects(),ObjIdx)
IF Objects()\isSelected = 1
ProcedureReturn #True
ENDIF
ProcedureReturn #False
EndProcedure
Procedure CheckTotalSelected()
PROTECTED Total.l = 0
ForEach Objects()
IF Objects()\isSelected
Total = Total + 1
ENDIF
Next
ProcedureReturn Total
EndProcedure
Procedure CheckSelected()
ForEach Objects()
IF Objects()\isSelected
ProcedureReturn ListIndex(Objects())
ENDIF
Next
ProcedureReturn -1
EndProcedure
Procedure GetFirstSelectedIndex()
PROTECTED MinOrder.l = 999999
PROTECTED FirstIdx.l = -1
PROTECTED idx.l = 0
ForEach Objects()
IF Objects()\isSelected
IF Objects()\SelectionOrder < MinOrder
MinOrder = Objects()\SelectionOrder
FirstIdx = idx
ENDIF
ENDIF
idx + 1
Next
ProcedureReturn FirstIdx
EndProcedure
Procedure AddSelection(ObjIdx.i)
SelectElement(Objects(),ObjIdx)
Objects()\isSelected = 1
SelectionCounter + 1
Objects()\SelectionOrder = SelectionCounter
EndProcedure
Procedure RemoveSelection(ObjIdx.i)
SelectElement(Objects(),ObjIdx)
Objects()\isSelected = 0
Objects()\SelectionOrder = 0
EndProcedure
Procedure ClearSelection()
ForEach Objects()
Objects()\isSelected = 0
Objects()\SelectionOrder = 0
Next
SelectionCounter = 0
EndProcedure
Procedure DrawGrid()
IF StartDrawing(ImageOutput(OffscreenImage))
DrawingMode(#PB_2DDrawing_Default)
Box(0, 0, OutputWidth(), OutputHeight(), RGB(255, 255, 255))
For x.l = 0 To OutputWidth() Step #GRID_SIZE
For y.l = 0 To OutputHeight() Step #GRID_SIZE
LineXY(x, y, x, y, RGB(220, 220, 220))
Next
Next
StopDrawing()
ENDIF
EndProcedure
Procedure DrawObjects()
IF StartDrawing(ImageOutput(OffscreenImage))
ForEach Objects()
SELECT Objects()\Type
CASE "BTN"
DrawingMode(#PB_2DDrawing_Default)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(240, 240, 240))
DrawingMode(#PB_2DDrawing_Outlined)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(0, 0, 0))
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(Objects()\X + 5, Objects()\Y + 5, Objects()\Text, RGB(0, 0, 0))
CASE "LABEL"
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(Objects()\X, Objects()\Y, Objects()\Text, RGB(0, 0, 0))
CASE "EDIT"
DrawingMode(#PB_2DDrawing_Default)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(255, 255, 255))
DrawingMode(#PB_2DDrawing_Outlined)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(128, 128, 128))
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(Objects()\X + 3, Objects()\Y + 3, Objects()\Text, RGB(128, 128, 128))
CASE "PHOTO"
DrawingMode(#PB_2DDrawing_Default)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(200, 200, 200))
DrawingMode(#PB_2DDrawing_Outlined)
Box(Objects()\X, Objects()\Y, Objects()\Width, Objects()\Height, RGB(0, 0, 0))
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(Objects()\X + Objects()\Width/2 - 25, Objects()\Y + Objects()\Height/2 - 5, Objects()\Text, RGB(100, 100, 100))
ENDSELECT
IF Objects()\isSelected
DrawingMode(#PB_2DDrawing_Outlined)
Box(Objects()\X - 2, Objects()\Y - 2, Objects()\Width + 4, Objects()\Height + 4, RGB(0, 120, 215))
DrawingMode(#PB_2DDrawing_Default)
Box(Objects()\X + Objects()\Width - #RESIZE_HANDLE, Objects()\Y + Objects()\Height - #RESIZE_HANDLE, #RESIZE_HANDLE, #RESIZE_HANDLE, RGB(0, 120, 215))
Box(Objects()\X + Objects()\Width - #RESIZE_HANDLE + 1, Objects()\Y + Objects()\Height - #RESIZE_HANDLE + 1, #RESIZE_HANDLE - 2, #RESIZE_HANDLE - 2, RGB(255, 255, 255))
ENDIF
Next
StopDrawing()
ENDIF
EndProcedure
Procedure AddObject(Type.s)
AddElement(Objects())
Objects()\Type = Type
Objects()\X = 20
Objects()\Y = 20
SELECT Type
CASE "BTN"
Objects()\Width = 100
Objects()\Height = 22
ButtonCounter + 1
Objects()\Text = "Button " + RSet(Str(ButtonCounter), 2, "0")
CASE "LABEL"
Objects()\Width = 60
Objects()\Height = 20
LabelCounter + 1
Objects()\Text = "Label " + RSet(Str(LabelCounter), 2, "0")
CASE "EDIT"
Objects()\Width = 150
Objects()\Height = 20
EditCounter + 1
Objects()\Text = "Edit " + RSet(Str(EditCounter), 2, "0")
CASE "PHOTO"
Objects()\Width = 100
Objects()\Height = 100
PhotoCounter + 1
Objects()\Text = "Photo " + RSet(Str(PhotoCounter), 2, "0")
ENDSELECT
ClearSelection()
AddSelection(ListSize(Objects()) - 1)
EndProcedure
Procedure FindObjectAt(X.l, Y.l)
idx = ListSize(Objects()) - 1
LastElement(Objects())
Repeat
IF X >= Objects()\X And X <= Objects()\X + Objects()\Width And
Y >= Objects()\Y And Y <= Objects()\Y + Objects()\Height
ProcedureReturn idx
ENDIF
idx - 1
Until Not PreviousElement(Objects())
ProcedureReturn -1
EndProcedure
Procedure.s CheckResizeHandle(X.l, Y.l, ObjIdx.i)
SelectElement(Objects(),ObjIdx)
IF Objects()\isSelected = 1
IF X >= Objects()\X + Objects()\Width - #RESIZE_HANDLE And
X <= Objects()\X + Objects()\Width And
Y >= Objects()\Y + Objects()\Height - #RESIZE_HANDLE And
Y <= Objects()\Y + Objects()\Height
ProcedureReturn "SE"
ENDIF
ENDIF
ProcedureReturn ""
EndProcedure
Procedure RedrawCanvas()
DrawGrid()
DrawObjects()
IF StartDrawing(CanvasOutput(#Canvas_Designer))
DrawImage(ImageID(OffscreenImage), 0, 0)
StopDrawing()
ENDIF
EndProcedure
Procedure SaveFile(bFull)
File$ = SaveFileRequester("Save Design (Full)", "design.txt", "Text Files (*.txt)|*.txt", 0)
IF File$
IF CreateFile(0, File$)
IF bFull
WriteStringN(0, "COUNTER_SECTION")
WriteStringN(0, "BUTTON_COUNTER=" + Str(ButtonCounter))
WriteStringN(0, "LABEL_COUNTER=" + Str(LabelCounter))
WriteStringN(0, "EDIT_COUNTER=" + Str(EditCounter))
WriteStringN(0, "PHOTO_COUNTER=" + Str(PhotoCounter))
WriteStringN(0, "END_COUNTER_SECTION")
WriteStringN(0, "")
ENDIF
ForEach Objects()
WriteStringN(0, "TYPE=" + Objects()\Type)
WriteStringN(0, "X=" + Str(Objects()\X))
WriteStringN(0, "Y=" + Str(Objects()\Y))
WriteStringN(0, "WIDTH=" + Str(Objects()\Width))
WriteStringN(0, "HEIGHT=" + Str(Objects()\Height))
WriteStringN(0, "TEXT=" + Objects()\Text)
WriteStringN(0, "IMAGEPATH=" + Objects()\ImagePath)
WriteStringN(0, "---")
Next
CloseFile(0)
MessageRequester("Success", "Full design saved!", #PB_MessageRequester_Info)
ENDIF
ENDIF
EndProcedure
Procedure LoadDesign()
File$ = OpenFileRequester("Load Design", "", "Text Files (*.txt)|*.txt", 0)
IF File$ And FileSize(File$) > 0
ClearList(Objects())
ClearSelection()
ButtonCounter = 0
LabelCounter = 0
EditCounter = 0
PhotoCounter = 0
IF ReadFile(0, File$)
PROTECTED InCounterSection.i = #False
While Not Eof(0)
Line$ = ReadString(0)
IF Line$ = "COUNTER_SECTION"
InCounterSection = #True
Continue
ELSEIF Line$ = "END_COUNTER_SECTION"
InCounterSection = #False
Continue
ENDIF
IF InCounterSection
IF Left(Line$, 15) = "BUTTON_COUNTER="
ButtonCounter = Val(Mid(Line$, 16))
ELSEIF Left(Line$, 14) = "LABEL_COUNTER="
LabelCounter = Val(Mid(Line$, 15))
ELSEIF Left(Line$, 13) = "EDIT_COUNTER="
EditCounter = Val(Mid(Line$, 14))
ELSEIF Left(Line$, 14) = "PHOTO_COUNTER="
PhotoCounter = Val(Mid(Line$, 15))
ENDIF
Continue
ENDIF
IF Line$ = "---"
Continue
ENDIF
IF Left(Line$, 5) = "TYPE="
AddElement(Objects())
Objects()\Type = Mid(Line$, 6)
ELSEIF Left(Line$, 2) = "X="
Objects()\X = Val(Mid(Line$, 3))
ELSEIF Left(Line$, 2) = "Y="
Objects()\Y = Val(Mid(Line$, 3))
ELSEIF Left(Line$, 6) = "WIDTH="
Objects()\Width = Val(Mid(Line$, 7))
ELSEIF Left(Line$, 7) = "HEIGHT="
Objects()\Height = Val(Mid(Line$, 8))
ELSEIF Left(Line$, 5) = "TEXT="
Objects()\Text = Mid(Line$, 6)
ELSEIF Left(Line$, 10) = "IMAGEPATH="
Objects()\ImagePath = Mid(Line$, 11)
ENDIF
Wend
CloseFile(0)
RedrawCanvas()
MessageRequester("Success", "Design loaded!", #PB_MessageRequester_Info)
ENDIF
ENDIF
EndProcedure
Procedure DeleteSelected()
PROTECTED isDelete.l = 0
ForEach Objects()
IF Objects()\isSelected = 1
DeleteElement(Objects())
isDelete = 1
ENDIF
Next
IF isDelete > 0
RedrawCanvas()
ENDIF
EndProcedure
Procedure AlignLeft()
PROTECTED MinX.l = 999999
ForEach Objects()
IF Objects()\isSelected = 1
IF Objects()\X < MinX
MinX = Objects()\X
ENDIF
ENDIF
Next
IF MinX <> 999999
ForEach Objects()
IF Objects()\isSelected = 1
Objects()\X = MinX
ENDIF
Next
RedrawCanvas()
ENDIF
EndProcedure
Procedure AlignRight()
PROTECTED MaxRight.l = -999999
ForEach Objects()
IF Objects()\isSelected = 1
PROTECTED RightEdge.l = Objects()\X + Objects()\Width
IF RightEdge > MaxRight
MaxRight = RightEdge
ENDIF
ENDIF
Next
IF MaxRight <> -999999
ForEach Objects()
IF Objects()\isSelected = 1
Objects()\X = MaxRight - Objects()\Width
ENDIF
Next
RedrawCanvas()
ENDIF
EndProcedure
Procedure AlignTop()
PROTECTED MinY.l = 999999
ForEach Objects()
IF Objects()\isSelected = 1
IF Objects()\Y < MinY
MinY = Objects()\Y
ENDIF
ENDIF
Next
IF MinY <> 999999
ForEach Objects()
IF Objects()\isSelected = 1
Objects()\Y = MinY
ENDIF
Next
RedrawCanvas()
ENDIF
EndProcedure
Procedure AlignBottom()
PROTECTED MaxBottom.l = -999999
ForEach Objects()
IF Objects()\isSelected = 1
PROTECTED BottomEdge.l = Objects()\Y + Objects()\Height
IF BottomEdge > MaxBottom
MaxBottom = BottomEdge
ENDIF
ENDIF
Next
IF MaxBottom <> -999999
ForEach Objects()
IF Objects()\isSelected = 1
Objects()\Y = MaxBottom - Objects()\Height
ENDIF
Next
RedrawCanvas()
ENDIF
EndProcedure
Procedure MatchSize()
IF CheckTotalSelected() < 2
ProcedureReturn
ENDIF
PROTECTED FirstIdx.l = GetFirstSelectedIndex()
IF FirstIdx < 0
ProcedureReturn
ENDIF
SelectElement(Objects(), FirstIdx)
PROTECTED TargetWidth.l = Objects()\Width
PROTECTED TargetHeight.l = Objects()\Height
ForEach Objects()
IF Objects()\isSelected = 1 And ListIndex(Objects()) <> FirstIdx
Objects()\Width = TargetWidth
Objects()\Height = TargetHeight
ENDIF
Next
RedrawCanvas()
EndProcedure
Procedure DistributeVertical()
SpacingStr$ = GetGadgetText(#String_Spacing)
PROTECTED CustomSpacing.l = Val(SpacingStr$)
IF CustomSpacing < 0 : CustomSpacing = 5 : ENDIF
NewList YPositions.FormObject()
ForEach Objects()
IF Objects()\isSelected = 1
AddElement(YPositions())
YPositions()\Y = Objects()\Y
YPositions()\Height = Objects()\Height
YPositions()\X = ListIndex(Objects())
ENDIF
Next
SortStructuredList(YPositions(), #PB_Sort_Ascending, OffsetOf(FormObject\Y), TypeOf(FormObject\Y))
FirstElement(YPositions())
PROTECTED CurrentY.l = YPositions()\Y
ForEach YPositions()
PROTECTED ObjIdx.l = YPositions()\X
SelectElement(Objects(), ObjIdx)
Objects()\Y = SnapToGrid(CurrentY)
CurrentY = Objects()\Y + Objects()\Height + CustomSpacing
Next
RedrawCanvas()
EndProcedure
Procedure RenumberObjects()
PROTECTED BtnNum.l = 0
PROTECTED LblNum.l = 0
PROTECTED EdtNum.l = 0
PROTECTED PhtNum.l = 0
ForEach Objects()
SELECT Objects()\Type
CASE "BTN"
BtnNum + 1
Objects()\Text = "Button " + RSet(Str(BtnNum), 2, "0")
CASE "LABEL"
LblNum + 1
Objects()\Text = "Label " + RSet(Str(LblNum), 2, "0")
CASE "EDIT"
EdtNum + 1
Objects()\Text = "Edit " + RSet(Str(EdtNum), 2, "0")
CASE "PHOTO"
PhtNum + 1
Objects()\Text = "Photo " + RSet(Str(PhtNum), 2, "0")
ENDSELECT
Next
ButtonCounter = BtnNum
LabelCounter = LblNum
EditCounter = EdtNum
PhotoCounter = PhtNum
RedrawCanvas()
EndProcedure
Procedure NewDesign()
Result.i = MessageRequester("New Design", "Clear all objects and start new?", #PB_MessageRequester_YesNo)
IF Result = #PB_MessageRequester_Yes
ClearList(Objects())
ClearSelection()
ButtonCounter = 0
LabelCounter = 0
EditCounter = 0
PhotoCounter = 0
RedrawCanvas()
ENDIF
EndProcedure
OpenWindow(#Win_Main, 0, 0, 900, 650, "My DB Form Designer "+#Version$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget)
CanvasGadget(#Canvas_Designer, 10, 10, 700, 550)
TextGadget(#LABEL_OBJECT, 720, 10, 160, 20, "Object:")
ButtonGadget(#Btn_AddButton, 720, 30, 160, 28, "Add Button")
ButtonGadget(#Btn_AddLabel, 720, 65, 160, 28, "Add Label")
ButtonGadget(#Btn_AddEdit, 720, 100, 160, 28, "Add Edit")
ButtonGadget(#Btn_AddPhoto, 720, 135, 160, 28, "Add Photo")
ButtonGadget(#Btn_Delete, 720, 170, 160, 28, "Delete Selected")
TextGadget(#LABEL_ALIGNMENT, 720, 210, 160, 20, "Alignment:")
ButtonGadget(#Btn_AlignLeft, 720, 235, 75, 25, "Left")
ButtonGadget(#Btn_AlignRight, 805, 235, 75, 25, "Right")
ButtonGadget(#Btn_AlignTop, 720, 265, 75, 25, "Top")
ButtonGadget(#Btn_AlignBottom, 805, 265, 75, 25, "Bottom")
ButtonGadget(#Btn_MatchSize, 720, 295, 160, 25, "Match Size (1st)")
ButtonGadget(#Btn_DistributeV, 720, 325, 160, 25, "Distribute Vertical")
ButtonGadget(#Btn_Renumber, 720, 355, 160, 25, "Renumber Objects")
TextGadget(#Text_Spacing, 720, 390, 100, 20, "V-Spacing (px):")
StringGadget(#String_Spacing, 820, 388, 60, 22, "5")
TextGadget(#LABEL_FILE, 720, 420, 160, 20, "File:")
ButtonGadget(#Btn_New, 720, 440, 160, 28, "New Design")
ButtonGadget(#Btn_Save, 720, 475, 160, 28, "Save Design (Full)")
ButtonGadget(#Btn_SaveClean, 720, 510, 160, 28, "Save Design (Clean)")
ButtonGadget(#Btn_Load, 720, 545, 160, 28, "Load Design")
TextGadget(#Text_Info, 10, 570, 700, 60, "Ctrl+Click to multi-select. Drag to move. Drag handle to resize." + #LF$ + "Selection confirmed on mouse release. Objects snap to 5px grid.")
OffscreenImage = CreateImage(#PB_Any, 700, 550)
DrawGrid()
Procedure ResizeControls()
PROTECTED WinW.l = WindowWidth(#Win_Main)
PROTECTED WinH.l = WindowHeight(#Win_Main)
PROTECTED CanvasW.l = WinW - 190
PROTECTED CanvasH.l = WinH - 90
PROTECTED InfoY.l = WinH - 70
PROTECTED ButtonX.l = WinW - 170
IF CanvasW < 400 : CanvasW = 400 : ENDIF
IF CanvasH < 300 : CanvasH = 300 : ENDIF
ResizeGadget(#Canvas_Designer, 10, 10, CanvasW, CanvasH)
ResizeGadget(#LABEL_OBJECT, ButtonX, 10, 160, 20)
ResizeGadget(#Btn_AddButton, ButtonX, 30, 160, 27)
ResizeGadget(#Btn_AddLabel, ButtonX, 65, 160, 27)
ResizeGadget(#Btn_AddEdit, ButtonX, 100, 160, 27)
ResizeGadget(#Btn_AddPhoto, ButtonX, 135, 160, 27)
ResizeGadget(#Btn_Delete, ButtonX, 170, 160, 27)
ResizeGadget(#LABEL_ALIGNMENT, ButtonX, 210, 160, 20)
ResizeGadget(#Btn_AlignLeft, ButtonX, 235, 75, 25)
ResizeGadget(#Btn_AlignRight, ButtonX + 85, 235, 75, 25)
ResizeGadget(#Btn_AlignTop, ButtonX, 265, 75, 25)
ResizeGadget(#Btn_AlignBottom, ButtonX + 85, 265, 75, 25)
ResizeGadget(#Btn_MatchSize, ButtonX, 295, 160, 25)
ResizeGadget(#Btn_DistributeV, ButtonX, 325, 160, 25)
ResizeGadget(#Btn_Renumber, ButtonX, 355, 160, 25)
ResizeGadget(#Text_Spacing, ButtonX, 390, 100, 20)
ResizeGadget(#String_Spacing, ButtonX + 100, 388, 60, 22)
ResizeGadget(#LABEL_FILE, ButtonX, 420, 160, 20)
ResizeGadget(#Btn_New, ButtonX, 440, 160, 27)
ResizeGadget(#Btn_Save, ButtonX, 475, 160, 27)
ResizeGadget(#Btn_SaveClean, ButtonX, 510, 160, 27)
ResizeGadget(#Btn_Load, ButtonX, 545, 160, 27)
ResizeGadget(#Text_Info, 10, InfoY, CanvasW, 60)
IF IsImage(OffscreenImage)
FreeImage(OffscreenImage)
ENDIF
OffscreenImage = CreateImage(#PB_Any, CanvasW, CanvasH)
RedrawCanvas()
EndProcedure
Repeat
Event = WaitWindowEvent()
SELECT Event
CASE #PB_Event_SizeWindow
ResizeControls()
CASE #PB_Event_Gadget
SELECT EventGadget()
CASE #Btn_AddButton
AddObject("BTN")
RedrawCanvas()
CASE #Btn_AddLabel
AddObject("LABEL")
RedrawCanvas()
CASE #Btn_AddEdit
AddObject("EDIT")
RedrawCanvas()
CASE #Btn_AddPhoto
AddObject("PHOTO")
RedrawCanvas()
CASE #Btn_Delete : DeleteSelected()
CASE #Btn_AlignLeft : AlignLeft()
CASE #Btn_AlignRight : AlignRight()
CASE #Btn_AlignTop : AlignTop()
CASE #Btn_AlignBottom : AlignBottom()
CASE #Btn_MatchSize : MatchSize()
CASE #Btn_DistributeV : DistributeVertical()
CASE #Btn_Renumber : RenumberObjects()
CASE #Btn_New : NewDesign()
CASE #Btn_Save : SaveFile(1)
CASE #Btn_SaveClean : SaveFile(0)
CASE #Btn_Load : LoadDesign()
CASE #Canvas_Designer
SELECT EventType()
CASE #PB_EventType_LeftButtonDown
X = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseX)
Y = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseY)
ClickedObj.i = FindObjectAt(X, Y)
HasMoved = #False
PendingClickObj = ClickedObj
PendingCtrlPressed = #False
IF GetAsyncKeyState_(#VK_CONTROL) & $8000
PendingCtrlPressed = #True
ENDIF
IF ClickedObj >= 0
IF CheckTotalSelected() = 1 And IsSelected(ClickedObj)
ResizeEdge = CheckResizeHandle(X, Y, ClickedObj)
ELSE
ResizeEdge = ""
ENDIF
IF ResizeEdge <> ""
ResizeMode = #True
DragStartX = X
DragStartY = Y
SelectElement(Objects(), ClickedObj)
DragObjX = Objects()\X
DragObjY = Objects()\Y
DragObjW = Objects()\Width
DragObjH = Objects()\Height
ELSE
IF IsSelected(ClickedObj)
DragMode = #True
DragStartX = X
DragStartY = Y
ENDIF
ENDIF
ENDIF
CASE #PB_EventType_LeftButtonUp
IF PendingClickObj >= 0 And Not HasMoved And Not ResizeMode
IF PendingCtrlPressed
IF IsSelected(PendingClickObj)
RemoveSelection(PendingClickObj)
ELSE
AddSelection(PendingClickObj)
ENDIF
ELSE
ClearSelection()
AddSelection(PendingClickObj)
ENDIF
RedrawCanvas()
ELSEIF PendingClickObj < 0 And Not HasMoved And Not PendingCtrlPressed
ClearSelection()
RedrawCanvas()
ENDIF
DragMode = #False
ResizeMode = #False
ResizeEdge = ""
PendingClickObj = -1
HasMoved = #False
CASE #PB_EventType_MouseMove
IF ResizeMode And CheckTotalSelected() = 1
HasMoved = #True
X = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseX)
Y = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseY)
DX = X - DragStartX
DY = Y - DragStartY
ForEach Objects()
IF Objects()\isSelected
NewW.l = DragObjW + DX
NewH.l = DragObjH + DY
IF NewW < 20 : NewW = 20 : ENDIF
IF NewH < 20 : NewH = 20 : ENDIF
Objects()\Width = SnapToGrid(NewW)
Objects()\Height = SnapToGrid(NewH)
RedrawCanvas()
Break
ENDIF
Next
ELSEIF DragMode And CheckTotalSelected() > 0
HasMoved = #True
X = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseX)
Y = GetGadgetAttribute(#Canvas_Designer, #PB_Canvas_MouseY)
DX = X - DragStartX
DY = Y - DragStartY
ForEach Objects()
IF Objects()\isSelected
Objects()\X = SnapToGrid(Objects()\X + DX)
Objects()\Y = SnapToGrid(Objects()\Y + DY)
IF Objects()\X < 0 : Objects()\X = 0 : ENDIF
IF Objects()\Y < 0 : Objects()\Y = 0 : ENDIF
ENDIF
Next
DragStartX = X
DragStartY = Y
RedrawCanvas()
ENDIF
ENDSELECT
ENDSELECT
CASE #PB_Event_CloseWindow
Break
ENDSELECT
ForEver
End