initialize module when map loaded

2596
7
Jump to solution
08-28-2019 03:42 AM
CarstenSchumann
Occasional Contributor

I have a dockpane which presents some information from a standalone-table. All examples I found Map.GetStandaloneTable. However during startup - in particular when the dockpane and its related module are initialized - there is no such map:

protected override bool Initialize()
{
	QueuedTask.Run(() => 
	{
		var map = // MapView.Active is null at this point
		var table = map.GetStandaloneTable("MyTable");
		// do something with the table
	}));
	return true;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Is there a way to load a table without the map being fully loaded? Alternativly I would initialize the module when the map is fully loadedwhich assumes there is an event which I can register.

0 Kudos
1 Solution

Accepted Solutions
CarstenSchumann
Occasional Contributor

I solved my problem with the table by registering for the MapViewInitializedEvent:

protected override bool Initialize()
{
	MapViewInitializedEvent.Subscribe(x => QueuedTask.Run(() => 
	{
		var table = x.MapView.Map.GetStandaloneTable(...);
		this._myList.AddRange(/* get data from table */);
	}
	return true;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

However I still stumble on presenting the tables information within my dockpane, which is attached to the modules MyList-property:

internal class Dockpane1ViewModel : DockPane
{
	public ReadOnlyObservableCollection<Reservation> MyList => Module1.MyList;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

From my understanding the Modules Initialize-method is called as soon as my dopckpane is first shown. As the MyList-property within MyModule is initialized within its constructor:

class Module1 : Module
{
	public ReadOnlyObservableCollection<Reservation> MyList { get; } = new ReadOnlyObservableCollection<Reservation>(_myList);
	protected override bool Initialize() { ... }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

both Dockpane1ViewModel.MyList and Module1.MyList should reference the exact same collection. Thus when my list is updated within Module1, this should be reflected in the ViewModel and thus in the pane. Or am I missing some binding from DockpaneViewmodel.MyList to Module.Mylist?

After all my solution now looks like this:

//Module1.cs

private readonly ObservableCollection<Reservation> _myList= new ObservableCollection<Reservation>();
public ReadOnlyObservableCollection<Reservation> MyList{ get; } = new ReadOnlyObservableCollection<Reservation>(this._myList);


protected override bool Initialize()
{
	MapViewInitializedEvent.Subscribe(x => QueuedTask.Run(() => 
	{
		var table = x.MapView.Map.GetStandaloneTable(...);
		this._myList.AddRange(/* get data from table */);
	}
	return true;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
// Dockpane1ViewModel.cs


public ReadOnlyObservableCollection<Reservation> MyList { get; } = Module1.Current.MyList ;

private readonly object m_LockObject = new object();
public Dockpane1ViewModel()
{
	BindingOperations.EnableCollectionSynchronization(this.MyList, this.m_LockObject);
}‍‍‍‍‍‍‍‍‍‍
// Reservation.cs

public class Reservation : INotifyPropertyChanged
{
	public string Name
	{
		get => _name;
		set
		{
			_name = value;
			this.OnPropertyChanged(nameof(Name));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

	/// <inheritDoc />
	protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
	{
		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

And finally within some command I update a Reservation:

// MyCommand.cs

protected override void OnClick()
{
	QueuedTask.Run(() => Module.Current.MyList.First().Name = "MyNewName");
}‍‍‍‍‍‍

The key here was just  three points:

  1. our collection itself implements INotifyCollectionChanged and thus fires event when items are added/removed
  2. our items implement INotifyPropertyChanged so we get an event when a member within our data-object is updated
  3. we need to bind the list to our view using BindingOperations.EnableCollectionSynchronization(this.MyList, this.m_LockObject because the items are modified on another thread, and thus the events would never get to the UI.

View solution in original post

7 Replies
Wolf
by Esri Regular Contributor
Esri Regular Contributor

You can use: ActiveMapViewChangedEvent to subscribe to an event triggered when there is a newly 'incoming' mapview.  You can do a  github search for 'ActiveMapViewChangedEvent' on arcgis-pro-sdk community samples to find samples snippets for this pattern.

0 Kudos
CarstenSchumann
Occasional Contributor

I solved my problem with the table by registering for the MapViewInitializedEvent:

protected override bool Initialize()
{
	MapViewInitializedEvent.Subscribe(x => QueuedTask.Run(() => 
	{
		var table = x.MapView.Map.GetStandaloneTable(...);
		this._myList.AddRange(/* get data from table */);
	}
	return true;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

However I still stumble on presenting the tables information within my dockpane, which is attached to the modules MyList-property:

internal class Dockpane1ViewModel : DockPane
{
	public ReadOnlyObservableCollection<Reservation> MyList => Module1.MyList;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

From my understanding the Modules Initialize-method is called as soon as my dopckpane is first shown. As the MyList-property within MyModule is initialized within its constructor:

class Module1 : Module
{
	public ReadOnlyObservableCollection<Reservation> MyList { get; } = new ReadOnlyObservableCollection<Reservation>(_myList);
	protected override bool Initialize() { ... }
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

both Dockpane1ViewModel.MyList and Module1.MyList should reference the exact same collection. Thus when my list is updated within Module1, this should be reflected in the ViewModel and thus in the pane. Or am I missing some binding from DockpaneViewmodel.MyList to Module.Mylist?

After all my solution now looks like this:

//Module1.cs

private readonly ObservableCollection<Reservation> _myList= new ObservableCollection<Reservation>();
public ReadOnlyObservableCollection<Reservation> MyList{ get; } = new ReadOnlyObservableCollection<Reservation>(this._myList);


protected override bool Initialize()
{
	MapViewInitializedEvent.Subscribe(x => QueuedTask.Run(() => 
	{
		var table = x.MapView.Map.GetStandaloneTable(...);
		this._myList.AddRange(/* get data from table */);
	}
	return true;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
// Dockpane1ViewModel.cs


public ReadOnlyObservableCollection<Reservation> MyList { get; } = Module1.Current.MyList ;

private readonly object m_LockObject = new object();
public Dockpane1ViewModel()
{
	BindingOperations.EnableCollectionSynchronization(this.MyList, this.m_LockObject);
}‍‍‍‍‍‍‍‍‍‍
// Reservation.cs

public class Reservation : INotifyPropertyChanged
{
	public string Name
	{
		get => _name;
		set
		{
			_name = value;
			this.OnPropertyChanged(nameof(Name));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;

	/// <inheritDoc />
	protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
	{
		PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

And finally within some command I update a Reservation:

// MyCommand.cs

protected override void OnClick()
{
	QueuedTask.Run(() => Module.Current.MyList.First().Name = "MyNewName");
}‍‍‍‍‍‍

The key here was just  three points:

  1. our collection itself implements INotifyCollectionChanged and thus fires event when items are added/removed
  2. our items implement INotifyPropertyChanged so we get an event when a member within our data-object is updated
  3. we need to bind the list to our view using BindingOperations.EnableCollectionSynchronization(this.MyList, this.m_LockObject because the items are modified on another thread, and thus the events would never get to the UI.
CarstenSchumann
Occasional Contributor

"Thus when my list is updated within Module1, this should be reflected in the ViewModel and thus in the pane. Or am I missing some binding from DockpaneViewmodel.MyList to Module.Mylist?" Assuming we have a binding for our viewmodels list:

BindingOperations.EnableCollectionSynchronization(this.MyList, this.m_LockObject);

updating the list within the Module will be reflected within the dockpane. However this seems to apply only for deletes/inserts into that list. Modifications on a single element within that list are not notified. 

0 Kudos
CarstenSchumann
Occasional Contributor

If the Reservation-type also implements INotifyPropertyChanged, our ReadOnlyObservableCollection will automatically recieve the events and update the UI.

0 Kudos
Wolf
by Esri Regular Contributor
Esri Regular Contributor

What is the type of this.MyList?  in general there are two issues complicating the work here: 

1) When you add a new row to your list (or whatever class you are using) an event has to be triggered (via INotifyPropertyChanged) that notifies the UI of the change, so that the UI can refresh the display to show the new row's data.

2) When you add a new row from a non UI thread (which is likely what you're doing), the add event notification doesn't go to the UI thread (by default it only goes to the thread where it's been triggered), unless you implement something like: 

BindingOperations.EnableCollectionSynchronization

i will make a small sample and try this out.

- Wolf

0 Kudos
Wolf
by Esri Regular Contributor
Esri Regular Contributor

It turns out that this sample: https://github.com/Esri/arcgis-pro-sdk-community-samples/tree/master/Map-Exploration/IdentifyWindow already implements the functionality that you need in terms of displaying data using DataGrid.  Look at AttributeDockpane and its ViewModel.  This sample is using a DataTable, so it will only work for relatively small datasets since the UI Notification event is not triggered until all data has been loaded.  The advantage of using a DataTable object is that it dynamically handles any table definition (columns, types, etc) you load (as demonstrated in the sample).  

0 Kudos
CarstenSchumann
Occasional Contributor

Thanks for your reply. That sample indeed is helpful in some way - as you mentioned it shows how to display data within a grid. 

My workflow however is a bit different. I have a list of objects (type Reservation, implements INotifyPropertyChanged). Now I´m updating some data within a standalone-table, whereby also some of those items within the list get updated (e.g. lets suppose each Reservation has a Name). You´re right, this happens on another thread, so I have to use BindingOperations.EnableCollectionSynchronization of course.

I don´t need a notification when the list itself changes - which can easily be achieved via the (ReadOnly-)ObservableCollection, but when the items within that list are modified.

All this WPF-stuff is quite new to me, we implemented ArcObjects and WinForms for years, so I may miss the obvious. I would appreciate if we could continue this on DevSummit in Berlin, as I have a working solution for now. Hope to see you there.

I think I will show my final code within my answer.

0 Kudos