Using MapWinGIS with MS-Access VBA

Dear MapWinGIS programmers and users,
I am new to MapWinGIS (MWG). I would like to use MWG as a viewer and select tool for a nature conservation area cadastre in Microsoft Access in connection with a PostgrSQL / PostGIS database (PgDB) as a backend. So far I have managed to implement a MWG window in an Access form and read the required data from the PgDB into the MapWinGIS window. Zoom and pan tools aren’t a problem either.
For my application, however, I still only need a select tool, which reads the area ID for the selected area in MWG from the associated table so that I can continue to use it as a search parameter in Access.
Conversely, I would like a text search in the factual data to zoom in on the identified area in MWG.
Unfortunately, the MapWinGIS documentation that is available on the Internet does not appear to be up to date. I have made experiments with e.g. “cmSelection”, “cmIdentify”, “SelectBoxFinal” etc. I used the MWG Reference Manual (2007) as a guide. Unfortunately the VB documentation is apparently not optimized for VBA. When transferring code snippets into Access, many errors occur as follows (highlighted in red):

Since I’m not a professional programmer, I’m a little bit desperate.
Is there a change here to get help for my concerns? Otherwise I will have to distance myself from my project.
I would be very happy to receive answers and help.

Best regards DetlevF

Hi Detlev,

here is an example of how you can implement a rectangle-select-tool in MS Access:


'****************************************************************
'****************************************************************
Private Sub MapMain_SelectBoxFinal(ByVal Left As Long, ByVal right As Long, ByVal bottom As Long, ByVal Top As Long)
On Error GoTo Error_Handler
On Error Resume Next
'---- Variables ----
Dim varError            As DAO.Error
Dim extTemp             As MapWinGIS.Extents
Dim sfTemp              As MapWinGIS.Shapefile

Dim dblLeft             As Double
Dim dblRight            As Double
Dim dblBottom           As Double
Dim dblTop              As Double

Dim intLayerHandle      As Integer
Dim varResult           As Variant
Dim lngResult_Start     As Long
Dim lngResult_End       As Long
Dim lngResult_Counter   As Long

Dim bolSuccess          As Boolean
'---------------------------------------
strErrorMessage = vbNullString

If (Me.MapMain.CursorMode = tkCursorMode.cmSelection) Then
    
    intLayerHandle = <Somewhere you have to get the LayerHandle of the Layer you want to select records of>
    
    Set sfTemp = Me.MapMain.Shapefile(intLayerHandle)

    'Reproject the given Box-Scree-Coordinates to MapCoordinates
    Me.MapMain.PixelToProj Left, Top, dblLeft, dblTop
    Me.MapMain.PixelToProj right, bottom, dblRight, dblBottom
    
    Set extTemp = New MapWinGIS.Extents
    extTemp.SetBounds dblLeft, dblBottom, 0, dblRight, dblTop, 0
    
    sfTemp.SelectNone
    
    'Get the shapes that intersect the drawn rectangle on the map
    If sfTemp.SelectShapes(extTemp, 0#, MapWinGIS.SelectMode.INTERSECTION, varResult) Then
    
        If IsArray(varResult) Then
            lngResult_Start = LBound(varResult)
            lngResult_End = UBound(varResult)
            
            For lngResult_Counter = lngResult_Start To lngResult_End
            
                sfTemp.ShapeSelected(varResult(lngResult_Counter)) = True
        
            Next
        End If
    End If

    Me.MapMain.Redraw
End If
Function_End:

    Set sfTemp = Nothing
    Set extTemp = Nothing

    If Len(strErrorMessage) > 0 Then _
        Debug.Print strErrorMessage
    
Exit Sub
Error_Handler:
    Debug.Print "------------- Errors in MapMain_SelectBoxFinal ---------"
    For Each varError In DBEngine.Errors: Debug.Print varError.Description: Next
    Debug.Print "-----------------------------------------------------"
    strErrorMessage = "Error in MapMain_SelectBoxFinal: " + Err.Description + " / " + CStr(Err.Number)
    Resume Function_End
End Sub

You need to activate the cmSelection-CursorMode and after you’ve drawn your rectangle with that tool in the map, this event gets fired.

If you then later want to access the selected records you can use the function:

 Dim  sfTemp  as MapWinGIS.Shapefile

 set sfTemp = Me.MapMain.Shapefile(intLayerHandle)

 set sfTemp = sfTemp.ExportSelection

And then you have all selected records accessible through this ShapeFile-Object.

Hope that helps.

Cheers
Stefan

Hello Stefan,
thanks for your quick answer. I’ll try it. I’ll get in touch with you regarding the progress. Currently but I’m a bit busy.
Thanks and best regards
Detlev

Hello @DetlevF

I’m not familiar with the MWG Reference Manual you are referring to. Have you seen the online documentation? Although it is not fully up-to-date, it should be helpful.

Regarding VBA:

  • You cannot allocate on the same line as the declaration, so you need to split the Dims and the News onto separate lines.

  • Procedures (not returning a value) should not include the parentheses around the parameters.

  • In terms of setting up event handlers, if VBA is like VB6, you probably Dim the map control With Events, and then you would be able to set up handlers.

I hope that helps.

Regards,
Jerry.

Hello @jerryfaust,
The latest documentation in VB that I found on the Internet is the MapWinGIS Reference Manual, dated July 23, 2007. I use the online documentation extensively, but all of the code is written in C #. To get a look at it in VB, I use an online converter, but it doesn’t always work, e.g. with the classes etc. from MapWinGIS.
thanks again
Detlev

Hello @jTati
I now tested the code for selecting the shapes in my application and it works very well! Thanks for that again. But so far, I couldn’t get an access to the ShapeFile-Object, the function you suggestet in your second codesnippet. To the shapefile.ExportSelection it’s said, the selected objects are stored in a new instance. How may I become access to this instance?
best regards
Detlev

Hi Detlev,

fine that the event handler works for you.

If you look at the three lines of code:
The second line attaches the shapefile-Variable sfTemp to the original ShapeFile, and the third line attaches this variable only to the records, that are selected, this is actualy the instance the documentation talks about. So whatever you now want to do with the selected records, - do it with this shapefile-instance.
For example: If you apply the saveas() method to this object, then you save all selected shapes in a new ESRI-Shapefile.

have a good night
Stefan

Hi Stefan,
Thank you again for your great help. I tried to put your export code in a function. Unfortunately the “sfTemp” parameter is not accepted.
Anyway, I have now programmed the export routine in the sub-procedure “SelecBoxFinal”, namely after the line “Me.TestMap.Redraw” (see the attached code). This functionality works fine:

Private Sub TestMap_SelectBoxFinal(ByVal Left As Long, ByVal right As Long, ByVal bottom As Long, ByVal Top As Long)
On Error GoTo Error_Handler
On Error Resume Next
'---- Variables ----
Dim varError            As DAO.Error
Dim extTemp             As MapWinGIS.Extents
Dim sfTemp              As MapWinGIS.Shapefile
Dim sfTxt               As String
Dim sfNum               As Integer
Dim str_SaveAs          As String

Dim dblLeft             As Double
Dim dblRight            As Double
Dim dblBottom           As Double
Dim dblTop              As Double

Dim intLayerHandle      As Integer
Dim varResult           As Variant
Dim lngResult_Start     As Long
Dim lngResult_End       As Long
Dim lngResult_Counter   As Long

Dim bolSuccess          As Boolean
'---------------------------------------
strErrorMessage = vbNullString

If (Me.TestMap.CursorMode = tkCursorMode.cmSelection) Then
    
    intLayerHandle = TestMap.get_LayerHandle(0)
    
    Set sfTemp = Me.TestMap.Shapefile(intLayerHandle)

    'Reproject the given Box-Scree-Coordinates to MapCoordinates
    Me.TestMap.PixelToProj Left, Top, dblLeft, dblTop
    Me.TestMap.PixelToProj right, bottom, dblRight, dblBottom
    
    Set extTemp = New MapWinGIS.Extents
    extTemp.SetBounds dblLeft, dblBottom, 0, dblRight, dblTop, 0
    
    sfTemp.SelectNone
    
    'Get the shapes that intersect the drawn rectangle on the map
    If sfTemp.SelectShapes(extTemp, 0#, MapWinGIS.SelectMode.INTERSECTION, varResult) Then
        If IsArray(varResult) Then
            lngResult_Start = LBound(varResult)
            lngResult_End = UBound(varResult)
            
            For lngResult_Counter = lngResult_Start To lngResult_End
            
                sfTemp.ShapeSelected(varResult(lngResult_Counter)) = True
        
            Next
        End If
    End If


    Me.TestMap.Redraw
    
    '************************************************************************
    'Test for save selected shapefile from this Procedure
    '************************************************************************

    Set sfTemp = Me.TestMap.Shapefile(intLayerHandle)
    Set sfTemp = sfTemp.ExportSelection
    
    str_SaveAs = "C:\temp\SelectTest.*"
        If Dir(str_SaveAs) <> "" Then
            Kill str_SaveAs
        End If
    
    sfTemp.SaveAs ("C:\temp\SelectTest.shp")
    SelectTest.Close

End If
Function_End:

    Set sfTemp = Nothing
    Set extTemp = Nothing

    If Len(strErrorMessage) > 0 Then _
        Debug.Print strErrorMessage
    
Exit Sub
Error_Handler:
    Debug.Print "------------- Errors in MapMain_SelectBoxFinal ---------"
    For Each varError In DBEngine.Errors: Debug.Print varError.Description: Next
    Debug.Print "-----------------------------------------------------"
    strErrorMessage = "Error in MapMain_SelectBoxFinal: " + Err.Description + " / " + CStr(Err.Number)
    Resume Function_End
End Sub

I save the selection as “SelectTest.shp” on my hard drive. However, when I open the file in a GIS, it only contains the selected shapes, but no factual data. Do you have any idea what’s going wrong?

Hi Detlev,

in your implementation this selection gets saved everytime the user uses the select tool. If that’s what you wanted to do, - then this approach is absolutely fine.

Keep in mind: The SelectBoxFinal-Eventhandler creates a selection in your MapLayer (=ShapeFile-Object).
You can afterwards access this selection from any other procedure you create, - you only need to create a shapefile-object that points to your MapLayer containing this selection.
This is what these lines of code in your EventHandler do:

intLayerHandle = <Somewhere you have to get the LayerHandle of the Layer you want to select records of>

Set sfTemp = Me.MapMain.Shapefile(intLayerHandle)

So if you are in a different function/sub inside of the Module of your form, you can use exactly these lines to get a handle to the layer with the selection and can do whatever you want to do with your selected records.

If you want to access this Layer from any other Module, you first need to get an handle to your Map-Object, like this:

Dim sfTemp              As MapWinGIS.Shapefile
Dim objTemp             As Object
Dim frmTemp             As Access.Form

On Error Resume Next
    'Get a handle to the Access-Form that contains the MapControl
    Set frmTemp = Application.Forms("<Name of Form containing the MapControl>")
Err.Clear
On Error GoTo Function_End

If frmTemp Is Nothing Then
    'If the form is not open yet you need to open it to get a Handle to the form
    DoCmd.OpenForm "<Name of Form containing the MapControl>"
    Set frmTemp = Application.Forms("<Name of Form containing the MapControl>")
End If

'Now you assign your object-Variable to the Map-Object on the form
Set objTemp = frmTemp.Controls("<name of Map-Control>")

intLayerHandle = <Somewhere you have to get the LayerHandle of the Layer you want to select records of>

'Get an handle to the MapLayer containing the selection
Set sfTemp = objTemp.Shapefile(intLayerHandle)

'Reuse the ShapeFile-Variable to get access to the selection within this MapLayer
set sfTemp = sfTemp.ExportSelection

Have Fun!
Stefan

Hi @jTati ,
thanks again, but right now I’m a little confused. I thought a layer handle gives the “number” of a layer that it occupies in a map window in a session. If I only opened one layer, I thought it would be “0”. So I assigned the value “get_LayerHandle (0)” to the variable “intLayerHandle”.
I understand your lines of code to mean that the variable “sfTemp” is assigned the selection from the ShapeFile object, which I select with “cmSelection”. I have now saved this selection as a test in an external ShapeFile to see whether the routine works properly. The selected shapes are saved in the ShapeFile properly, but all fields of the attribute table have the value “Null”.
It is already clear to me that I need another procedure, as in my request, to retrieve the data record ID from the attribute table of the selected ShapeFile (although I do not yet know exactly how to do it :-).
But so far in this context, I first have the question of whether it is correct to assign the value from “TestMap.get_LayerHandle (0)” to the variable “intLayerHandle”. Now I think I’m on the wrong track.

Hi Detlev,

if you have only one layer, your assumption of 0 as input for the Map.LayerHandle() function is correct, since its position will allways be the first.

intLayerHandle = <mapobject>.LayerHandle(<Position of Layer in MapLayers>)

The layerhandle, - the value of intLayerHandle of the statement above -, is an internal value and can be any value.
You can use the “ExportSelection”-Output to access the values of your original Shapefile:

    Set sfTemp = sfTemp.ExportSelection
    
    For counter = 1 To sfTemp.NumShapes
        debug.print CStr(sfTemp.CellValue(<name of a column>, counter - 1))
    Next counter

…in case you want to do something with them.

Stefan

Hi @jTati ,
I would like to give you some feedback. Your assistance has been very successful for me. I’ll try to modify your last tip a bit and read the selected data into an array:

Dim tmpTbl() As Variant
Dim qryDef As DAO.QueryDef
Set sfTemp = Me.TestMap.Shapefile(intLayerHandle)
Set sfTemp = sfTemp.ExportSelection

For counter = 1 To sfTemp.NumShapes
    ReDim Preserve tmpTbl(counter)
    tmpTbl(counter) = (sfTemp.CellValue(sfTemp.FieldIndexByName("fl_id"), counter - 1))
Next counter


Set qryDef = CurrentDb.CreateQueryDef(qryFlaechen)
DoCmd.OpenQuery "qryFlaechen"

I would use the array directly in an SQL query and program a form filter from it. So far it’s still a bit tricky for me and the code isn’t still complete. But I think I’ll get ahead with your suggestions. I won’t be able to deal with the matter again until tomorrow.

So far, thank you very much.

Detlev

@jTati It works!!!

'************************************************************************
'Synchronize MapWindow selection with form gtb_flaeche
'************************************************************************

strWerteliste = ""
Set sfTemp = sfTemp.ExportSelection

For counter = 1 To sfTemp.NumShapes
    If strWerteliste <> "" Then
        strWerteliste = strWerteliste & ", "
    End If
    strWerteliste = strWerteliste & (sfTemp.CellValue(sfTemp.FieldIndexByName("fl_id"), counter - 1))
Next counter

Forms!gtb_flaeche.FilterOn = False
Forms!gtb_flaeche.Filter = "fl_id in (" & strWerteliste & ")"
Forms!gtb_flaeche.FilterOn = True
Forms!gtb_flaeche.Refresh

Many Thanks!

Hi @jerryfaust , hi @jTati ,

I have now tested my MapWinGIS viewer in the Access database. In principle, the routine works well and I am very grateful for it! However, the routine is sometimes unstable. Whenever I scroll too fast in the MapWindow or if I change the tool during rendering, Access as the master application crashes. Do you have an explanation for this? Is there an error handling for this?

Hi Detlev,

most probably i have.
You can find it in some of my posts: I had this behaviour and it nearly made me abandon MapWinGIS in MS Access. I’m using an OpenStreetMap-TileServer-Background-Map,- as you do as i see in your screenshot, - and MS Access can obviously not handle the amount of tilesloaded-Events that get fired by the MapWinGIS.ocx.
I found, that even if your application doesn’t honour this event, it occours in the background somewhere and makes MS Access instable.
I compiled my own version of MapWinGIS without a tilesloaded-Event and it runs now absolutely stable, without crashing.
If you need such a version, i can show you, what to change on the source-code, and you can then compile your own version of this component - it’s fairly easy, if you have Visual Studio community edition installed somewhere.

Stefan

P.S.
I have a compiled v5.2.4 without tilesloaded event, - i can send you a download link, if you send me your e-mail address.

Thanks, Stefan, for your support on this issue.

I recall our conversations related to this, and now reading your post, I wonder whether it would make sense at some point to add a new Global variable to the OCX for those working in environments like Access, to effectively toggle your code changes on or off, and thus not require a custom build.

Feel free to share your thoughts on this.

Regards,
Jerry.

Hello Stefan,

Thank you for your informative answer. I’m only getting around to posting now.
You say Access crashes while rendering OSM. To get around the problem, you compiled an OCX version without a tile server implementation that you would provide to me. First of all, thank you for the nice offer!
However, I need map information with a background map. So if I integrated another tile server (e.g. GoogleMaps) or a WMS server, I would probably have the same problem again, right?

Hi Detlev,

you got me wrong:
My version of the control is still fully functional, - you would only have a problem,
if your appliction uses the TilesLoaded-Event.
You can still use a tileserver to give you a background map view, - or better said:
with this version it is now possible to do so in ms access, wether with the tilesloaded event active in the control ms access crashes.

Ok, sorry for the misunderstanding. I would then like to try whether your OCX version works for me. My email address is:
d.finke@dvl.org
Since I have to reinstall Visual Studio on my computer and I am currently I’m very busy, but I probably won’t be able to give you feedback before the weekend.