Select to view content in your preferred language

Cycle through multiple projects

150
2
Jump to solution
a month ago
Asimov
by
New Contributor III

I'm working on an AddIn that should produce rasters and pdf from multiple layout projects. For every project defined, it should load it and do some work based on user input and preferences, producing TIFFs and PDFs requested. 

The project list is dynamic and defined by the user. With a single project the goal is easily achievable listening to the MapViewInitializedEvent, so after the MapView is initialized I can access to it and run all the code I need, but I need to figure out a way to do it with multiple projects inside a cycle. 

The cycle should run through all the projects in the list and do the same thing, with a pseudo-code similar to this:

 

foreach (var project in ProjectList)
{
    string currentProjectPath = Path.Combine(baseFolderPath, project.BasePath, project.FileName);
    if (!File.Exists(currentProjectPath))
        continue;

    Project.Current.SetDirty(false);

    if (_mapViewInitEventToken != null)
        MapViewInitializedEvent.Unsubscribe(_mapViewInitEventToken);
    _mapViewInitEventToken = MapViewInitializedEvent.Subscribe(OnMapViewInitialized);

    await Project.OpenAsync(currentProjectPath);
}

 

The OnMapViewInitialized handler is the entry point from which I am able to run the code I need. Now, this code will never work and I can understand why: after a project is opened, the code goes on to the next project in the list and another project is loaded before MapViewInitialized Event has the chance to be fired. In fact if I run this code I only get the last project in the list correctly worked. 

I need some other event or method that allows my code to wait for the work I need to do on a project before going on to the next one. Listening to ProjectOpened event does not help, since I can't get to access the map from there. Anyone with a suggestion would be highly appreciated, thanks

1 Solution

Accepted Solutions
Asimov
by
New Contributor III

As a final update I'll post the solution I adopted to make it work with no issues, in case it can help anyone who'll face a similar problem in the future.

I realized there was something going on with the threads involed in my previous approach, so I changed to something cleaner: instead of cycling directly through the projects, I defined a custom event in the class I use as the engine of the solution (i.e. where all the logic implemented, basicly the same class where the cycle was in my previous approach). That made me able to get rid of the cycle and let ArcGIS Pro handle the events normally and without having to wait for them. 

Essentially the caller starts the process on the first project, and let the engine do his things. It can take any amount of time and there's no issue about that because it's not inside a cycle anymore. After everything is done on the project, the event is fired and the caller can repeat the process again with the next project.

To set up my custom event all I needed was to add a delegate and an event handler to my class:

public delegate void RasterOpCompletedEventHandler(object sender, OpCompletedEventArg e);

public class RasterizerEngine
{
    public static event RasterOpCompletedEventHandler OnOpCompleted;
    [...]
}

RasterOpCompletedEventHandler is a type I also defined, where I just added all the properties I need to send to the listener of my event.

The listener can be any class, in my case it is a DockPane from which the user can start the process. On that class I just listen to the event defining the handler to execute when the event is fired:

RasterizerEngine.OnOpCompleted += OpCompleted;

Pretty plain and standard usage of the events. OpCompleted contains the logic that actually replaces the cycle, since it calls again the engine class to start another job with a new project.

Hope I gave a sufficiently clear idea of the solution, cannot post all the code involved because it's quite too much and not really relevant to the specific issue, but in case anyone needs some more details, feel free to ask.

View solution in original post

0 Kudos
2 Replies
Asimov
by
New Contributor III

So far I've been able to find a way to cycle through the projects by using a static boolean flag to store whether the operations are running and waiting for it, "sleeping" on a background thread that doesn't block the UI. Something like this:

 

 

foreach (var project in ProjectList)
{
    string currentProjectPath = Path.Combine(baseFolderPath, project.BasePath, project.FileName);
    if (!File.Exists(currentProjectPath))
        continue;
		
	opRunning = true;

	if (_mapViewInitEventToken != null)
		MapViewInitializedEvent.Unsubscribe(_mapViewInitEventToken);
	_mapViewInitEventToken = MapViewInitializedEvent.Subscribe(OnMapViewInitialized);

	if (_layoutViewEventToken != null)
		LayoutViewEvent.Unsubscribe(_layoutViewEventToken);
	_layoutViewEventToken = LayoutViewEvent.Subscribe(OnLayoutViewEvent);

    Project.Current.SetDirty(false);

    await Project.OpenAsync(currentProjectPath);
	
	while (opRunning)
		Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, (DispatcherOperationCallback)delegate (object unused) { return null; }, null);
}

 

 

 

MapViewInitializedEvent and LayoutViewEvent are used to trigger the operations; when they are finished the set the opRunning flag to false and the main cycle will go on to the next project.

Everything seems to work fine, but now I'm stuck with the SetDirty issue (for which I posted a separate topic here ). For some reason, the call to Project.Current.SetDirty(false) doesn't work, and obviously I don't want the user to be prompted with the save changes message. 

Still needing suggestions about this, I'm totally open to change the approach if there is some better way to wait for the operations to be finished inside the cycle.

0 Kudos
Asimov
by
New Contributor III

As a final update I'll post the solution I adopted to make it work with no issues, in case it can help anyone who'll face a similar problem in the future.

I realized there was something going on with the threads involed in my previous approach, so I changed to something cleaner: instead of cycling directly through the projects, I defined a custom event in the class I use as the engine of the solution (i.e. where all the logic implemented, basicly the same class where the cycle was in my previous approach). That made me able to get rid of the cycle and let ArcGIS Pro handle the events normally and without having to wait for them. 

Essentially the caller starts the process on the first project, and let the engine do his things. It can take any amount of time and there's no issue about that because it's not inside a cycle anymore. After everything is done on the project, the event is fired and the caller can repeat the process again with the next project.

To set up my custom event all I needed was to add a delegate and an event handler to my class:

public delegate void RasterOpCompletedEventHandler(object sender, OpCompletedEventArg e);

public class RasterizerEngine
{
    public static event RasterOpCompletedEventHandler OnOpCompleted;
    [...]
}

RasterOpCompletedEventHandler is a type I also defined, where I just added all the properties I need to send to the listener of my event.

The listener can be any class, in my case it is a DockPane from which the user can start the process. On that class I just listen to the event defining the handler to execute when the event is fired:

RasterizerEngine.OnOpCompleted += OpCompleted;

Pretty plain and standard usage of the events. OpCompleted contains the logic that actually replaces the cycle, since it calls again the engine class to start another job with a new project.

Hope I gave a sufficiently clear idea of the solution, cannot post all the code involved because it's quite too much and not really relevant to the specific issue, but in case anyone needs some more details, feel free to ask.

0 Kudos