Microsoft Access 2007 Application.GlobalCallback not triggering/ program freezing

Feb 11, 2015 at 3:25 PM
Hello!

Using Win32 version 4.9.3.4 it seems the globalsettings.applicationcallback class fires only sporadically, and often not at all.

Open a instance of the Access form and set the callback using the example code provided in a delayed open function:
Public Function delayedMapLoad(geoID as string) as Long

Dim gs As new GlobalSettings
Set objCallback = New MwCallback
gs.ApplicationCallback = objCallback

//method then loads, reprojects, and adds serveral shapefile layers
//the uses the prefetch to load the tiles for a snapshot
fetchCounter = axMap.Tiles.Prefetch2(tilesExtents.xMin, tilesExtents.xMax, tilesExtents.yMin, tilesExtents.yMax, axMap.Tiles.CurrentZoom, axMap.Tiles.providerID, stopper)

//loop until callback sets picture to ready or there were no tiles to prefetch
Do until fetchCounter tiles = 0 or snapshotready
    DoEvents
Loop

//take snapshot
axmap.takepicture

delayedMapLoad = fetchCounter 

End Function
My MwCallback class is identical to the example code:
Implements ICallback

Private Sub ICallback_Error(ByVal KeyOfSender As String, ByVal ErrorMsg As String)
    Debug.Print ErrorMsg
End Sub

Private Sub ICallback_Progress(ByVal KeyOfSender As String, ByVal Percent As Long, ByVal Message As String)

   //set snapshotready = true when Percent = 100 and Message is correct
    Debug.Print Message & ": " & Percent
End Sub
However the message and percent next print to the log and any breakpoints added in the class just seem to be ignored or, more often, cause the program to crash. Without being able to debug without crashing I can't figure out what the issue is...

I do attempt to load shapefiles from a network drive I have gotten the error "The object has disconnected from its clients" on some occasions, so perhaps a slow/stalled connection is freezes Access. However, the class seems called only sporadically when I move those files locally.

Alternatively I have used the LoadtilesforSnapshot/TilesLoaded methods to accomplish the same thing, but the snapshots often appear to have little some missing tiles.

Should the applicationcallback be set elsewhere? Is my approach with the loop waiting for callback incorrect?
Perhaps I'm approaching all wrong. Any help would be greatly appreciated

Thanks very much!
Developer
Feb 11, 2015 at 6:41 PM
Hi, this functionality might be unstable since it's not covered with tests or demo app. I'll need to write some test code to suggest a proper way to do it and potentially to fix bugs (it's too late today to start it). The thing I definitely don't like about your approach is Do...Loop cycle for waiting. Such things should be avoided. To give me some context, what is the purpose of prefetching tiles in your scenario?

Regards,
Sergei
Feb 11, 2015 at 9:38 PM
Thanks for the response.

I realize the do...while waiting loop is less than ideal. I have a limited grasp of asynchronous execution in VBA so maybe you could suggest a better way to do it.

For context I need to embed the map in an Access report. Since Access 2007 doesn't seem to support the control in a report (at least I can't figure it out) I have to open a form, capture a snapshot, then embed it in the report to print.

Maybe it would be best to call a method to print from the MwCallback class, but it doesn't seem to firing.

Thanks so much!
Developer
Feb 12, 2015 at 9:44 PM
Edited Feb 12, 2015 at 9:56 PM
Hi, here is a solution that worked for me, it's in C# though, hope you'll be able to grasp it. It's recommended to use Map.LoadTilesForSnapShot for such scenario. The method won't return progress however (some gif progress indicator is the best available option to notify user that work is under way). ApplicationCallback has no affect here as nothing is reported during the loading.
private string _guid = "<>";    // must be something else than empty string

private void InitMap()
{
    // let's display something
    axMap1.Projection = tkMapProjection.PROJECTION_GOOGLE_MERCATOR;
    axMap1.TileProvider = tkTileProvider.OpenStreetMap;
    axMap1.KnownExtents = tkKnownExtents.keUSA;

    axMap1.TilesLoaded += axMap1_TilesLoaded;    // strange but plus is encoded
    new GlobalSettings() {ApplicationCallback = this};   // it's implemented by form itself; 
    // a regular implementation other than that
}

// this loads tiles for snapshot
private void btnCheckPrefetch_Click(object sender, EventArgs e)
{
    _guid = Guid.NewGuid().ToString();
    int width = axMap1.Width*2;  // width of screenshot in pixels, let's request 
    // magnified screenshot to make sure that tiles other than those in screen buffer are needed 
    axMap1.LockWindow(tkLockMode.lmLock);       // it's should be done by MapWinGIS itself; will be fixed
    axMap1.LoadTilesForSnapshot(axMap1.Extents as Extents, width, _guid, axMap1.TileProvider);
}

void axMap1_TilesLoaded(object sender, AxMapWinGIS._DMapEvents_TilesLoadedEvent e)
{
    if (e.key == _guid)     // to make sure that it's not a regular loading
    {
        var ext = axMap1.Extents as Extents;
        if (ext != null)   // just in case; it should always be not null
        {
            var img = axMap1.SnapShot3(ext.xMin, ext.xMax, ext.yMax, ext.yMin, axMap1.Width * 2);
            img.Save(@"d:\temp.png");
            MessageBox.Show("Snapshot has been taken");
        }
                
        // it may remain locked if loading fails for some reason; 
        // network connection problem for example
        axMap1.LockWindow(tkLockMode.lmUnlock);   
    }
}
As for your approach, it would be better simply call snapshot from ICallback_Progress (when 100 percent is reported) and not to use Do..Loop at all. Perhaps I'll test that scenario as well later on.
Feb 18, 2015 at 2:13 PM
Thanks again for the reply. I've tried using the TilesLoaded method many times with no success.

My only issue in translating from C# to VBA is the equivalent of the line:

axMap1.TilesLoaded += axMap1_TilesLoaded;

Even without an equivalent event assignment, I can get the TilesLoaded method to fire as you suggested, using the system time and a unique ID.
Public Sub CheckPrefetch()

    //load map as usual

    timeString = CStr(Now)
    Dim width As Integer
    width = axMap.width * 2
    
    axMap.LockWindow tkLockMode.lmLock
    axMap.LoadTilesForSnapshot axMap.extents, width, timeString, axMap.TileProvider
           
End Function

Private Sub axMap_TilesLoaded(ByVal SnapShot As Boolean, ByVal Key As String)

    If SnapShot And Key = timeString Then

        Dim myImage As New MapWinGIS.image
        myImage.UpsamplingMode = imHighQualityBicubic
        Set myImage = axMap.SnapShot3(axMap.extents.xMin, axMap.extents.xMax, axMap.extents.yMax, axMap.extents.yMin, axMap.width * 2)
        myImage.Save "C:\temp.jpg"
    
    End If

End Sub
However, I'm having an issue with scaling. When I set the Access map control to 512px (5.333") the debugger tells me that the axMap.width = 7,680. At that width, it seems that the Snapshot3 method fails, since the resulting image is too large. Setting any control width below 256px seems to result in either a crash or an image with severe scaling issues, presumably since the form isn't wide enough for even a single tile.

I can successfully take an image by hard coding a smaller width:
Set myImage = axMap.SnapShot3(axMap.extents.xMin, axMap.extents.xMax, axMap.extents.yMax, axMap.extents.yMin, 1024)
But the resulting snapshot has no tiles at all, even when setting the corresponding LoadTilesForSnapshot to the same resolution (1024 in that case). Obviously printing the snapshot again, after the tiles has buffered, does work, but it never seems to complete successfully the first time around.

Any advice you could give is appreciated.
Developer
Feb 20, 2015 at 7:08 AM
Here is another tip: try to pass extents which are slightly different from current map extents (requests with same extents may be considered as duplicated and dropped):
Set myImage = axMap.SnapShot3(axMap.extents.xMin - 1, axMap.extents.xMax + 1, axMap.extents.yMax + 1, axMap.extents.yMin - 1, axMap.width * 2)
This solved the issue with 256px width for me (I had it to). But I have no idea about 512px issue (naturally debugger reports that map width is 512 in that case and all works well).
axMap1.TilesLoaded += axMap1_TilesLoaded is C# syntax to attach event handler, your VBA code already doing it. Also no need to create image and settings sampling mode before snapshot call, a new instance will be returned all the same.

Hope it'll help,
Sergei