Select to view content in your preferred language

ComboBox in DockPane load not finished before Selecting

98
8
Jump to solution
Wednesday
TomLewis
Occasional Contributor

Using ArcGIS Pro SDK 3.3...

I have a bit of code that populates a combobox with options then selects the first one. The selection only works some of the time and is not predictable that I can tell. I added a Thread.Sleep(1000) and it works so this tells me it's definitely some sort of asynchronous action happening with the initial load of the combobox. I'm just wondering what the proper way to load, then select the first item would be.

Here is my code that works (with the sleep). It's overly complicated because the customer wants the currently selected location name first, then a blank line, then all of the available location names after that.

The combobox binding properties:

		private ObservableCollection<OffroadLocationNames> _locationNames;
		public ObservableCollection<OffroadLocationNames> LocationNames
		{
			get => _locationNames;
			set => SetProperty(ref _locationNames, value);
		}
		private OffroadLocationNames _selectedLocationName;
		public OffroadLocationNames SelectedLocationName
		{
			get => _selectedLocationName;
			set => SetProperty(ref _selectedLocationName, value);
		}

 

DockPane populate code: (I'm creating a local ObservableCollection then assigning it to the bound property just as a test to see if it made a difference... which it did not.)

			var locationList = new ObservableCollection<OffroadLocationNames>();
			if (!string.IsNullOrEmpty(pointData.TrafficFeature) &&  pointData.LocationID > 0)
			{
				OffroadLocationNames thisOne = Module1.Current.Cache.OffroadLocations.Data.FirstOrDefault(d => d.LocationId == pointData.LocationID);
				if (thisOne != null)
				{
					locationList.Add(thisOne);
					locationList.Add(new OffroadLocationNames() { LocationId = -1, LocationName = "" });
				}
				locationList.AddRange(Module1.Current.Cache.OffroadLocations.GetUnusedLocationNames(pointData.TrafficFeature));
				LocationNames = new ObservableCollection<OffroadLocationNames>(locationList);

				Thread.Sleep(1000);
				SelectedLocationName = LocationNames.First(); // OrDefault(ln => ln.LocationId ==  thisOne.LocationId);
			}

 

0 Kudos
1 Solution

Accepted Solutions
GKmieliauskas
Esri Regular Contributor

That's bad. You need return your list from MCT thread and then set property in UI thread.

        var locationList = await QueuedTask.Run(() =>
            {
			var locationList = new ObservableCollection<OffroadLocationNames>();
			if (!string.IsNullOrEmpty(pointData.TrafficFeature) &&  pointData.LocationID > 0)
			{
				OffroadLocationNames thisOne = Module1.Current.Cache.OffroadLocations.Data.FirstOrDefault(d => d.LocationId == pointData.LocationID);
				if (thisOne != null)
				{
					locationList.Add(thisOne);
					locationList.Add(new OffroadLocationNames() { LocationId = -1, LocationName = "" });
				}
				locationList.AddRange(Module1.Current.Cache.OffroadLocations.GetUnusedLocationNames(pointData.TrafficFeature));
				return locationList;

        });
				LocationNames = new ObservableCollection<OffroadLocationNames>(locationList);

				SelectedLocationName = LocationNames.First(); // OrDefault(ln => ln.LocationId ==  thisOne.LocationId);

or 

			var locationList = new ObservableCollection<OffroadLocationNames>();
			if (!string.IsNullOrEmpty(pointData.TrafficFeature) &&  pointData.LocationID > 0)
			{
				OffroadLocationNames thisOne = Module1.Current.Cache.OffroadLocations.Data.FirstOrDefault(d => d.LocationId == pointData.LocationID);
				if (thisOne != null)
				{
					locationList.Add(thisOne);
					locationList.Add(new OffroadLocationNames() { LocationId = -1, LocationName = "" });
				}
				locationList.AddRange(Module1.Current.Cache.OffroadLocations.GetUnusedLocationNames(pointData.TrafficFeature));
RunOnUIThread(() =>
                {
				LocationNames = new ObservableCollection<OffroadLocationNames>(locationList);

				SelectedLocationName = LocationNames.First(); // OrDefault(ln => ln.LocationId ==  thisOne.LocationId);
                });
}

        /// <summary>
        /// utility function to enable an action to run on the UI thread (if not already)
        /// </summary>
        /// <param name="action">the action to execute</param>
        /// <returns></returns>
        internal static Task RunOnUIThread(Action action)
        {
            if (OnUIThread)
            {
                action();
                return Task.FromResult(0);
            }
            else
                return Task.Factory.StartNew(action, System.Threading.CancellationToken.None, TaskCreationOptions.None, QueuedTask.UIScheduler);
        }

Sorry for code formating



View solution in original post

8 Replies
GKmieliauskas
Esri Regular Contributor

Hi,

Your properties set methods doesn't initiate NotifyPropertyChanged event. Your dockpane binding properties must look in code below:

		private ObservableCollection<OffroadLocationNames> _locationNames;
		public ObservableCollection<OffroadLocationNames> LocationNames
		{
			get => _locationNames;
			set => SetProperty(ref _locationNames, value, () => LocationNames);
		}
		private OffroadLocationNames _selectedLocationName;
		public OffroadLocationNames SelectedLocationName
		{
			get => _selectedLocationName;
			set => SetProperty(ref _selectedLocationName, value, () => SelectedLocationName);
		}
0 Kudos
TomLewis
Occasional Contributor

I made the changes and it is still not selecting the first item without the 1 second sleep.

This code is triggered by the user selecting a point on the map. If I select the point a second time without selecting any other point, it works. It's only on the first time loading the point that it fails to select. And to make it even more frustrating, it will occasionally work the first time.

Also, if I step through the code, the Selected property is getting properly set to the first item in the list... it's just not selecting it in dropdown.

0 Kudos
TomLewis
Occasional Contributor

Still no proper fix, but I have more clues...

I put a breakpoint on the SelectedLocationName setter. When it doesn't show the selection, it is getting a second call to the setter with value = null. This is being called from external code so I have no clue what is causing the call. It also happens on every other point I select. Basically, I select a point, the value is not displayed. Select another point, and it works fine. Selected another different point, no worky. So weird.

Also, I added the following code before I re-populate the combobox and the problem is fixed. But I see this as a kludge and would love to know what I can do to fix it.

SelectedLocationName = null;

 

0 Kudos
GKmieliauskas
Esri Regular Contributor

From what thread are you trying to update properties: MCT or UI?

0 Kudos
TomLewis
Occasional Contributor

It's in the MCT inside of a QueuedTask.Run.

0 Kudos
GKmieliauskas
Esri Regular Contributor

That's bad. You need return your list from MCT thread and then set property in UI thread.

        var locationList = await QueuedTask.Run(() =>
            {
			var locationList = new ObservableCollection<OffroadLocationNames>();
			if (!string.IsNullOrEmpty(pointData.TrafficFeature) &&  pointData.LocationID > 0)
			{
				OffroadLocationNames thisOne = Module1.Current.Cache.OffroadLocations.Data.FirstOrDefault(d => d.LocationId == pointData.LocationID);
				if (thisOne != null)
				{
					locationList.Add(thisOne);
					locationList.Add(new OffroadLocationNames() { LocationId = -1, LocationName = "" });
				}
				locationList.AddRange(Module1.Current.Cache.OffroadLocations.GetUnusedLocationNames(pointData.TrafficFeature));
				return locationList;

        });
				LocationNames = new ObservableCollection<OffroadLocationNames>(locationList);

				SelectedLocationName = LocationNames.First(); // OrDefault(ln => ln.LocationId ==  thisOne.LocationId);

or 

			var locationList = new ObservableCollection<OffroadLocationNames>();
			if (!string.IsNullOrEmpty(pointData.TrafficFeature) &&  pointData.LocationID > 0)
			{
				OffroadLocationNames thisOne = Module1.Current.Cache.OffroadLocations.Data.FirstOrDefault(d => d.LocationId == pointData.LocationID);
				if (thisOne != null)
				{
					locationList.Add(thisOne);
					locationList.Add(new OffroadLocationNames() { LocationId = -1, LocationName = "" });
				}
				locationList.AddRange(Module1.Current.Cache.OffroadLocations.GetUnusedLocationNames(pointData.TrafficFeature));
RunOnUIThread(() =>
                {
				LocationNames = new ObservableCollection<OffroadLocationNames>(locationList);

				SelectedLocationName = LocationNames.First(); // OrDefault(ln => ln.LocationId ==  thisOne.LocationId);
                });
}

        /// <summary>
        /// utility function to enable an action to run on the UI thread (if not already)
        /// </summary>
        /// <param name="action">the action to execute</param>
        /// <returns></returns>
        internal static Task RunOnUIThread(Action action)
        {
            if (OnUIThread)
            {
                action();
                return Task.FromResult(0);
            }
            else
                return Task.Factory.StartNew(action, System.Threading.CancellationToken.None, TaskCreationOptions.None, QueuedTask.UIScheduler);
        }

Sorry for code formating



TomLewis
Occasional Contributor

Awesome. I'll give that a shot.

 

Thanks!

0 Kudos
TomLewis
Occasional Contributor

Thanks again. That fixes the issue. I've only been doing ArcGIS Pro SDK work for 2 weeks and the different threads and what to do where is still a grey area for me... obviously. (Porting an ArcMap Extension)

It's starting to come together though.

0 Kudos