Hi Everyone,
I am not sure why I am unable to populate the selected layer's fields. I have two combo boxes in my dockpane, layers in the map and selected layer fields. It's weird that when I comment out the GetFieldAsync() method, I am able to populate the feature layers on the active map, but when I don't comment the method out, then both the layer combo box and the selected layer's fields combo box does not show. If you can help me troubleshoot this problem, I would appreciate the help. Here is codes below:
private FeatureLayer _selectedLayer;
// Selected feature layer
public FeatureLayer SelectedLayer
{
get { return _selectedLayer; }
set
{
SetProperty(ref _selectedLayer, value, () => SelectedLayer);
_ = GetFieldsAsync();
}
}
private async Task GetFieldsAsync()
{
LayerFields.Clear();
SelectedField.Clear();
if (SelectedLayer == null)
return;
await QueuedTask.Run((Action)(() =>
{
ObservableCollection<string> fields = new ObservableCollection<string>();
foreach (FieldDescription fd in SelectedLayer?.GetFieldDescriptions())
{
string shapeField = SelectedLayer.GetFeatureClass().GetDefinition().GetShapeField();
if (fd.Name == shapeField) continue;
fields.Add(fd.Name);
}
}));
}
Solved! Go to Solution.
Hi Thi,
I attached a 2.9 sample that should build and run in 2.8. Remember that when you add data to the UI as a result of an Event (I.E. layer changed events) then very likely your code will run on a non-UI thread. Also anything within QueuedTask.Run is running on a non-UI thread.
You are updating a list/combobox from a background thread, hence you have to take certain precautions. Like using:
System.Windows.Data.BindingOperations.EnableCollectionSynchronization(_layers, _lock);
This enables your observable collection for updates on non GUI threads. Also once you define and 'lock' an observable collection you can't assign a new collection (like you did with your field names). I attached a sample that shows FeatureLayers and field names on a Dockpane.
Hi Wolf,
Thanks for your help! After rereading your post, I understood what you meant. I am not able to open the example you attached. I recieved the following error message: "error : The project file cannot be opened. Unable to locate the .NET SDK. Check that it is installed and that the version specified in global.json (if any) matches the installed version." I have ArcGIS Pro SDK 2.8 installed. It looks like the SDK version does not match, most likely due to 3.x migration and SDK updates. Instead of creating a new collection, can you show me a short snippet of what I should be doing to see the layer's fields?
Thanks,
Thi
Hi Thi,
I attached a 2.9 sample that should build and run in 2.8. Remember that when you add data to the UI as a result of an Event (I.E. layer changed events) then very likely your code will run on a non-UI thread. Also anything within QueuedTask.Run is running on a non-UI thread.
For anyone having the same issue, I made a few changes to the DockpaneWithLayerFieldSelection, but both ways work:
internal class DockpaneWithLayerFieldSelectionViewModel : DockPane
{
private const string _dockPaneID = "DockpaneWithLayerFieldSelection_DockpaneWithLayerFieldSelection";
private ObservableCollection<FeatureLayer> _layers = new ObservableCollection<FeatureLayer>();
private ObservableCollection<string> _fieldNames = new ObservableCollection<string>();
private object _lock = new object();
protected DockpaneWithLayerFieldSelectionViewModel()
{
BindingOperations.EnableCollectionSynchronization(_layers, _lock);
BindingOperations.EnableCollectionSynchronization(_fieldNames, _lock);
LayersAddedEvent.Subscribe(OnLayersAdded);
LayersRemovedEvent.Subscribe(OnLayersRemoved);
ActiveMapViewChangedEvent.Subscribe(OnActiveMapViewChanged);
// just in case the map is already up
if (MapView.Active != null)
{
ActiveMapViewChangedEventArgs args = new ActiveMapViewChangedEventArgs(MapView.Active, null);
OnActiveMapViewChanged(args);
}
}
#region Bindings
public ObservableCollection<FeatureLayer> Layers
{
get { return _layers; }
}
private FeatureLayer _selectedLayer;
public FeatureLayer SelectedLayer
{
get { return _selectedLayer; }
set
{
SetProperty(ref _selectedLayer, value, () => SelectedLayer);
FieldNames.Clear();
if (SelectedLayer == null) return;
QueuedTask.Run(() =>
{
foreach (FieldDescription fd in SelectedLayer?.GetFieldDescriptions())
{
string shapeField = SelectedLayer.GetFeatureClass().GetDefinition().GetShapeField();
if (fd.Name == shapeField) continue;
FieldNames.Add(fd.Name);
}
});
}
}
public ObservableCollection<string> FieldNames
{
get { return _fieldNames; }
}
private string _selectedFieldName;
public string SelectedFieldName
{
get { return _selectedFieldName; }
set
{
SetProperty(ref _selectedFieldName, value, () => SelectedFieldName);
Selection = string.Empty;
if (SelectedFieldName == null) return;
Selection = $@"{SelectedLayer.Name}.{SelectedFieldName}";
}
}
private string _Selection;
public string Selection
{
get => _Selection;
set => SetProperty(ref _Selection, value);
}
/// <summary>
/// Text shown near the top of the DockPane.
/// </summary>
private string _heading = "Show Layer and Field Selection";
public string Heading
{
get => _heading;
set => SetProperty(ref _heading, value);
}
#endregion
#region Event Handlers
/// <summary>
/// The active map view changed, first clear layers and fields, then
/// refresh with new layers if incomingView is not null
/// </summary>
/// <param name="args"></param>
private void OnActiveMapViewChanged(ActiveMapViewChangedEventArgs args)
{
FieldNames.Clear();
Layers.Clear();
if (args.IncomingView != null)
{
var layers = args.IncomingView.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>();
Layers.AddRange(layers);
}
}
private void OnLayersRemoved(LayerEventsArgs args)
{
foreach (var layer in args.Layers.OfType<FeatureLayer>())
{
if (Layers.Contains(layer))
Layers.Remove((FeatureLayer)layer);
}
}
private void OnLayersAdded(LayerEventsArgs args)
{
foreach (var layer in args.Layers.OfType<FeatureLayer>())
{
Layers.Add((FeatureLayer)layer);
}
}
#endregion
/// <summary>
/// Show the DockPane.
/// </summary>
internal static void Show()
{
DockPane pane = FrameworkApplication.DockPaneManager.Find(_dockPaneID);
if (pane == null) return;
pane.Activate();
}
}
/// <summary>
/// Button implementation to show the DockPane.
/// </summary>
internal class DockpaneWithLayerFieldSelection_ShowButton : Button
{
protected override void OnClick()
{
DockpaneWithLayerFieldSelectionViewModel.Show();
}
}
}