Class to move about 20 pictures

Ronald de Graaf

New member
Local time
Today, 14:14
Joined
Sep 12, 2020
Messages
16
Hello,
I'm building a kind of floor plan.

For this I placed 50 image objects on a form. I am able to display certain images (for example a table for 4 persons or a table for 8 persons) on a selected image object.
What I want is that these image objects can be moved with MouseDown/MouseMove.

Now I can of course program that for all these objects at the events "MouseDown", "MouseMove" and "MouseUp" in VBA, but the same code has to be executed for all image objects.

I think this should be easy to achieve with a Class in which these events take place.
However, I've been working on it for 3 days now and I can't figure it out.

Can someone help me?

Thanks in advance!
 
If you have code that works for a single object can you post it? I can show you a class so it can work on any form for any object.
 
This will explain the process
 
@CJ_London posted some code how to move and resize

I modified this into a class so I can move as many images as I want. In the example there are three images. You still have to declare an MoveableImage and then initialize it.
Example for three
Code:
Option Compare Database
Option Explicit
Private mi_One As New moveableimage
Private mi_Two As New moveableimage
Private mi_Three As New moveableimage
Private Sub Form_Load()
  mi_One.Initialize Me.imgOne
  mi_Two.Initialize Me.imgTwo
  mi_Three.Initialize Me.imgThree
End Sub

The class is actually very short once you import in the code from CJ_London. This is the only new code I added.
Code:
Private WithEvents m_moveableimage As Access.Image
Public Sub Initialize(TheImage As Access.Image)
  Set moveableimage = TheImage
  moveableimage.OnMouseDown = "[Event Procedure]"
  moveableimage.OnMouseMove = "[Event Procedure]"
  moveableimage.OnMouseUp = "[Event Procedure]"
End Sub
Public Property Get moveableimage() As Access.Control
    Set moveableimage = m_moveableimage
End Property
Public Property Set moveableimage(ByVal objNewValue As Access.Control)
    Set m_moveableimage = objNewValue
End Property
Private Sub m_moveableimage_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
  ctlMouseDown Me.moveableimage, X, Y
End Sub
Private Sub m_moveableimage_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
  ctlMouseMove Me.moveableimage, X, Y
End Sub
Private Sub m_moveableimage_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
 ctlMouseUp
End Sub
I attached the original and my class.
 

Attachments

If you have code that works for a single object can you post it? I can show you a class so it can work on any form for any object.
Good Morning MajP,

Thanks for your reply.

I was thinking to change the 3 events (OnMouseDown, OnMouseMove and OnMouseUp) to work with a class.

Me.Controls(strControlName).OnMouseDown = "=ctlMouseDown(" & Me.Controls(strControlName).Name & ", X, Y)"
Me.Controls(strControlName).OnMouseMove = "=ctlMouseMove(" & Me.Controls(strControlName).Name & ", X, Y)"
Me.Controls(strControlName).OnMouseUp = "=ctlMouseUp(" & !fldFloorPlanID & ")"


When I don't add the code above, I can move 1 image object (img00). That works just fine.
But what I don't want to do is to write the same code for all image objects (img00 to img50)


Here's some part of the code:
----------------------------------------------------
Code:
Option Compare Database
Option Explicit
Dim movePic As Boolean
Dim xMouseDown As Long
Dim yMouseDown As Long


Private Sub Form_Open(Cancel As Integer)
    Dim rsTblFloorplan      As DAO.Recordset
    Dim strControlName      As String
    'On Error Resume Next
    Set rsTblFloorplan = CurrentDb.OpenRecordset("SELECT * FROM tblFloorPlan WHERE fldFloorID=1")
    If rsTblFloorplan.RecordCount > 0 Then
        With rsTblFloorplan
            Do While Not .EOF
                strControlName = "Img" & Format(!fldImgNumber, "00")
                Me.Controls(strControlName).Left = !fldFloorComponentX
                Me.Controls(strControlName).Top = !fldFloorComponentY
                Me.Controls(strControlName).OnMouseDown = "=ctlMouseDown(" & Me.Controls(strControlName).Name & ", X, Y)"
                Me.Controls(strControlName).OnMouseMove = "=ctlMouseMove(" & Me.Controls(strControlName).Name & ", X, Y)"
                Me.Controls(strControlName).OnMouseUp = "=ctlMouseUp(" & !fldFloorPlanID & ")"
                .MoveNext
            Loop
        End With
        Me.Repaint
    End If
End Sub
'

'* Code to move IMG00
'  How can I create a class wich handles the move of all IMGnn objects?
'  Or can this to be done in a Sub or Function?

Private Sub Img00_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    movePic = True
    xMouseDown = X
    yMouseDown = Y
End Sub
Private Sub Img00_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim ctlImg  As Control
    Set ctlImg = Me.ActiveControl
    On Error Resume Next
    If movePic Then
        Img00.Move Img00.Left + X - xMouseDown, Img00.Top + Y - yMouseDown
    End If
End Sub
Private Sub Img00_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)

    Dim rsTblFloorplan   As DAO.Recordset
    Set rsTblFloorplan = CurrentDb.OpenRecordset("SELECT * FROM tblFloorPlan WHERE fldFloorplanID=" & intFloorPlanID)
    If rsTblFloorplan.RecordCount > 0 Then
        rsTblFloorplan.Edit
        rsTblFloorplan!fldFloorComponentX = Img00.Left
        rsTblFloorplan!fldFloorComponentY = Img00.Top
        rsTblFloorplan.Update
    End If
    rsTblFloorplan.Close
    Set rsTblFloorplan = Nothing
    movePic = False

End Sub
----------------------------------------------------
 
Last edited:


Hi MajP,

Thanks again for your reply.
But in this case I have to decalre 50 private mi_ objects and have to initialize those 50 objects.

This works fine but why does this code not work:

Code:
Option Compare Database
Option Explicit
Private mi As New moveableimage


Private Sub Form_Load()
    Dim ctl As Control
    For Each ctl In Me.Controls 'scan through the controls
        If TypeName(ctl) = "Image" Then ' Take only Text Boxes
            mi.Initialize ctl
        End If
    Next
End Sub


When I run this I can see that the events are filled with [Event Procedure].
But only the last image object can move... ???

1660031916237.png
 
Unfortunately that will not work. You are creating a single instance of moveableimage. You just overwrite the variable.

What you can do is create one moveable image object, but change the which image is assigned to the object.

I tagged all my images with "Move"
Code:
Private mi As New moveableimage

Private Sub Form_Load()
  Dim ctrl As Access.Control
  For Each ctrl In Me.Controls
    If ctrl.Tag = "Move" Then
      ctrl.OnClick = "=InitializeMoveableImage('" & ctrl.Name & "')"
    End If
  Next ctrl
End Sub

Public Function InitializeMoveableImage(imageName As String)
  mi.Initialize Me.Controls(imageName)
End Function

I wanted to re-intialize in the mouse down event, but I got weird behavior. So I choose the click event. This forces the user to click the image first. Then drag and drop it.
You can try changing this line. Theoretically it would change the assigned image without forcing a click. You will see that it works for a while.
ctrl.OnMouseDown = "=InitializeMoveableImage('" & ctrl.Name & "')"
 

Attachments

Hi MajP,

Thanks again for your answer. I get the point of this. And this might be a better solution for what I want.

But I'm just wondering if I'm doing it the right way...

I have about 15 images that I would like an Admin (the person who can make the floorplan) to put on a form. These images must then be able to be moved so that, for example, the tables of the restaurant or cafe can be placed in the right place.
And it must of course be possible to place the same image several times on the form (after all, a restaurant often has the same tables). The dimensions of the image and its position should be stored in table tblFloorplan.

I wonder if there is no other way to accomplish this.

My idea was to first click on the image, then select an empty image object and provide this image object with the "picture".
And create an event (MouseUp) that stores the length/width and position of the image object and the name of the picture in a table.


When a restaurant employee opens the Floorplan, he may not be able to move the images, but can click to open/close a table.
I am creating a POS cash register. And so when making a Floorplan, I get stuck.....

I've attached the DB so you can see what I mean. And just maybe you have ideas about how this can be realized differently (read in a better way).
You would really help me a lot with this!
 

Attachments

Are you put the images into the grid below at those dimensions? Or do you want to pick an image from the top and put it anywhere and resize it?
 
Hi MajP,

There are supposed to be 2 screens:
1. The screen where the Floorplan can be created and updated
2. A screen in which the Floorplan is shown and where you can click on the objects (images or command buttons with images) to open the restaurant table (Eg. table 1023).

I'm working on the 1st screen right now. My first thought was to show the objects with images in the header of the form (there aren't that many). The manager must be able to drag these objects into the form and place them in the right place. Of course it must be possible to place an object (eg a 4-person table) on the form several times. Each object must be saved/updated to MouseUp in the tblFloorplan table.

2.
It must be possible for the employees in the service to consult the Floorplan and to click on the objects in order to see the corresponding invoice and to allocate additional orders to this table.

I have now placed 105 image objects in the form but I wonder if this is necessary... I wanted to make it so that when clicking on an image in the header (eg the 4 person table) it drags it could be sent to an image object on the form.

But I suspect it could be otherwise.

But as I said, I'm stuck because I don't really know which technique is best to use...

I hope my explanation has become a bit clearer and.... That you can help me on my way!

Thank you very much!
 
Here is a 90% solution. Needs a lot of testing. There is just a lot of little pieces that all have to synch. There is so many moving pieces you will just have to look at it before I can explain.
some explanation
1. I stacked all 105 images and made them invisible. I named them "lbl_1" to "lbl_105". I use code to do this
2. I unhide the next "unassigned" image
3. You can click on the other images to change the selected component type
4. You drag only the top one
5. once you drop it, I unhide the next available "unassigned" image. The dropped images are tagged as "assigned_ID". If it is a new image the ID is left as 0.

When the form loads
You need to change the combo to pick a floor
1. It first puts everything in its initial settings (stacked and hidden)
2. Then loops the query and assigns a control to a record. It knows what type of component so it can figure out what image to use. It puts it in the correct space

To Do
1. Need to add a method to delete a component from the plan or change its component type.
2. Lots of error checking

I added this table
tblFloorComponents tblFloorComponents

ComponentTypeIDComponentDescriptionNumberofSeatsImageNameIsTable
1​
Upper Right Counter
0​
Afbeeldinging181
No​
2​
Four-top, right / left seating
4​
Afbeelding187
Yes​
3​
Four-top, top/bottom seating
4​
Afbeelding188
Yes​
4​
Six-top, top/bottom seating
6​
Afbeelding189
Yes​
5​
Six-top, right/left seating
6​
Afbeelding190
Yes​
6​
Eight-top, top/bottom seating
8​
Afbeelding191
Yes​
7​
Eight-top, right/left seating
8​
Afbeelding192
Yes​
You need to finish it for the remaining images, I focused on the tables.
I modified your tblFloorPlan. I do not think that was correct.
The primary key in that table is "fldFlooPlanID". That is a bad name. It should be FloorPlanComponentID. That key uniquely describes a component within a floor plan.

FloorPlan.jpg

I
You first need to click on the image in the black box, then drag onto the floor plan. The other images only change the type of image.
 

Attachments

Good morning MajP,

WOW!
I think this is exactly what I want! Thank you very much for your help with this!

I'm going to study the code and develop the program further.

Now that I look at this one more question comes to my mind...

Isn't it also possible in Access to create a new object definition (eg ImageSlideable) that has the same properties as the Image object (inheritance)?
I would like to show a label under the image for the FloorComponents where IsTable=TRUE.

Is it therefore not possible, as soon as I click on a FloorComponent, to create a new ImageSlideable object and place it on the grid?



MajP, thank you so much again! You really helped me a lot with this. I can work with it now
 
Isn't it also possible in Access to create a new object definition (eg ImageSlideable) that has the same properties as the Image object (inheritance)?
I would like to show a label under the image for the FloorComponents where IsTable=TRUE.
Not sure what you mean here, but unlikely. Access is not really designed for this kind of graphic form manipulation. You are pretty limited. You never want to create controls on the fly in a runtime environment. So with concepts like this you start with a bunch of existing hidden controls in Access.
However, there is another type of form and controls that you can use in Access. There is also the MS Forms and MS Form controls. This is the same UserForm used in Excel. It can do dynamic controls and may be a better option for something like this. If I find some time I will see if I can redo this. This would give you more options
This example is a tree view made with a MS Forms userform. This is some extremely advanced code (I use it not build it).
You can build a many thousands of node tree and all the pieces are just textboxes, labels, and lines all created at runtime.

Better than that would be to build a Visual Studio front end, if you are a good coder. You can still have the backend in Access or switch to something like Sql Server. For this you can use Sql Server Express for free and VS for free. You can write in VB.NET if familiar with VBA. With VS you can build a User Control. Then you can add your control to a form and it could be composed of an image and label that work together. Again you can create these at runtime.

Access does not support inheritance. If it did I could have a MoveableImage class which is generic. Then i could create a MoveableFloorComponent which would be based on the generic MoveableImage. I could then add unique properties to this class such as FloorComponentID, AssociatedLabel, IsTable, NumberofSeats, etc.

So the techniques used in this demo are a little kludgy, but that is what you are limited to in Access. Instead of inheritance you build a composite class, a class that has properties that are other classes. One or more of those properties are access controls.
In the above example you have a several buttons that are properties of the navigation control class. The class then makes them seem to work as a single user control.

If you had real custom user controls, dynamic controls, and inheritance you could make something much better.

Is it therefore not possible, as soon as I click on a FloorComponent, to create a new ImageSlideable object and place it on the grid?
I went away from the grid idea, because of flexibility and not knowing all of your requirements. However, that may be a better way. If your form is "gridded" out with a set amount of image controls you could hide them all. I think then I would get rid of the drag and drop and use the left and right click of the mouse. When I right click a custom menu control appears with your table images shown in the menu. This would make it easy to delete or change the assigned floorcomponent. I think that would be easier than dragging and dropping. If I get time I will demo. Then you simply show or hide the image control. There are some advantages using the grid as you originally had it.

However in the current design you can just resize the image controls to fit more into the given space. With the grid idea it will be hard to resize. If you need more object in either horizontal or vertical you have to redesign the grid.
 
Dear MajP,

Thank you for your explanation.
And I know that Access is indeed not really suitable for this kind of "graphical" work.
I am fairly familiar with VB.NET. Have made quite a few applications with it in the past. But I'm not very familiar with CLASSES yet. And it's time for me to dig into it...

Incidentally, it seemed that a GRID had been created in the form, but that was not the case. Those were the 105 IMAGE objects that were neatly aligned on the form. I was now able to select an IMAGE object that was not yet in use (img_NN.picture="")
But the solution you have now made is much nicer and better!

I started this cash register project (POS) in MS-Access. Simply because the development (interface) and tables can be created and modified much faster in MS-Access. And to be honest, I like the "looks" of the controls in Access (gradient buttons, buttons with rounded corners, etc.) better than the controls in VB.NET

Once I fully understand the logic of this application for myself, I intend to rebuild it in VB.NET with a SQL Server database.

I don't know how familiar you are with VB.NET but if you have the time (and inclination [don't know if this is the right word]) to make the solution as you made it now for me in VB.NET I am eternally grateful to you. But I also understand very well if you don't have the time and inclination for that...

Now I'm going to go through the code you sent me very carefully.

And I'll take a look at the links you posted in your post.

You don't know how happy I am with your help so far!
Again thank you very much!!
 
I would like to show a label under the image for the FloorComponents where IsTable=TRUE.
If you go with the original grid idea then you will have to put a label under each image control in the grid. You would show and hide as appropriate. With the current design you have 105 hidden images stacked to start with. You would do all the same with 105 labels. Then when you drag the image you would just position the label under where you drop the image and show it. It is more coding and synchronizing these moving parts. With enough coding you can drag and drop either the label or the image. It would just require a lot more code, but you could get that effect.

FYI if you work with lots of controls it can be a big pain to try to move, select, format. You can write code to help. Lets assume you want a new grid with 20 images horizontal and 15 vertical. Each with a label underneath. This is kind of a pain to get them all in position and modify as needed. You can write code to build this in design view. Makes it a lot easier then moving and selecting by hand.

So here is how you open a form in design view (not for runtime) and add a control (button)
Code:
Public Sub MakeButton(frmName As String, TabName As String, PageNum As Integer)
  Const ctlWidth = (0.75 * 1440)
  Const ctlHeight = (0.25 * 1440)
  Static Added As Integer
  Dim frm As Access.Form
  Dim pgName As String
  Dim tabCtl As Access.TabControl
  Dim pg As Access.page
  Dim tabwidth As Long
  Dim tabHeight As Long
  Dim tabTop As Long
  Dim tabLeft As Long
 
  DoCmd.Close acForm, "frmButton"
  DoCmd.OpenForm frmName, acDesign
  Set frm = Forms(frmName)
  Set tabCtl = frm.Controls(TabName)
 
  tabwidth = tabCtl.Width
  tabHeight = tabCtl.Height
  tabTop = tabCtl.Top
  tabLeft = tabCtl.Left
 
  Set pg = tabCtl.Pages(PageNum)
  pgName = pg.Name
  Dim btn As Access.Control
  Added = Added + 1
  Set btn = Application.CreateControl(frmName, acCommandButton, , pgName)
  With btn
   .Width = ctlWidth
   .Height = ctlHeight
   .Left = tabCtl.Left + 1 * 1440
   .Top = tabCtl.Top + 1 * 1440
   .Caption = "Button" & Added
  End With
  With tabCtl
    .Top = tabTop
    .Width = tabwidth
    .Left = tabLeft
    .Height = tabHeight
  End With
  DoCmd.Close acForm, frmName, acSaveYes
  DoCmd.OpenForm frmName
  Forms(frmName).Controls(TabName) = PageNum

End Sub

Here is an example of positioning lots of controls into a grid with a given width and height.
Code:
Public Sub formatGrid(x As Long, y As Long)
  Const ctlWidth = (0.75 * 1440)
  Const ctlHeight = (0.25 * 1440)
  Const conStartLeft = (0.5 * 1440)
  Const conStartTop = (0.5 * 1440)
 
  Dim startLeft As Long
  Dim startTop As Long
  Dim lngRow As Long
  Dim lngCol As Long
  Dim intCounter As Long
  Dim ctl As Access.Control
 
  startLeft = conStartLeft
  startTop = conStartTop
 ' Call makeAllInvisible
  For lngRow = 1 To y
     For lngCol = 1 To x
     intCounter = intCounter + 1
     Set ctl = Me.Controls("lblGrid" & intCounter)
     With ctl
         .Height = ctlHeight
         .Width = ctlWidth
         .Left = startLeft
         .Top = startTop
         .Caption = "Row" & lngRow & " Col" & lngCol
         .FontSize = 8
         .Visible = True
         .Tag = lngRow & ";" & lngCol
         'Define the function here that they do when clicked.
         .OnClick = "=gridClick()"
       End With
       startLeft = startLeft + ctlWidth
     Next lngCol
     startLeft = conStartLeft
     startTop = startTop + ctl.Height
  Next lngRow
 
 End Sub
 
Hi MajP,

🙈🙈🙈

DoCmd.OpenForm frmName, acDesign

I am deeply ashamed. I've been developing in MS-Access for quite a few years now, but.... I really didn't know that this was possible
 
I am deeply ashamed. I've been developing in MS-Access for quite a few years now, but.... I really didn't know that this was possible
You can do this to help make development easier. Especially with formatting and positioning lots of controls.

However, do not do this in a runtime environment. People often ask if they can create forms and controls at runtime. In theory you can do this by opening a form in design view and hidden then save it then open it normally, but it is a really bad idea for lots of different reasons.

The workaround in Access is to start with hidden controls and make it look as if you are creating controls, as the demo shows. The attached demo has a bunch of hidden controls and makes it appear as if you are dynamically creating a grid
 

Attachments

Last edited:
Here is an update. If you double click on an dragged image you can then change the component or delete it from a pop up.
 

Attachments

Users who are viewing this thread

Back
Top Bottom