Call to ZoomTo() "breaks" event command triggers

204
4
Jump to solution
a month ago
Asimov
by
New Contributor III

I recently upgraded ArcGIS Pro and Visual Studio SDK Extensions to version 3.2.2, coming across a very strange behavior, which wasn't present in version 3.1

I have a ProWindow with several controls and logic in it, with MVVM pattern. One control is a datagrid which shows some business data in a pretty simple configuration:

<DataGrid Grid.Row="0" x:Name="dgBeni" ScrollViewer.CanContentScroll="True"
      Margin="0,5,0,0"
      VerticalAlignment="Top"
      Style="{DynamicResource Esri_DataGrid}"
      AutoGenerateColumns="False"
      HeadersVisibility="Column"
      RowHeaderWidth="0"
      IsReadOnly="True"
      Height="250"
      HorizontalScrollBarVisibility="Auto"
      VerticalScrollBarVisibility="Auto"
      SelectionMode="Single"
      ItemsSource="{Binding Path=CurrentItem.Beni}"
      SelectedItem="{Binding Path=CurrentItem.SelectedBene}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Cod Bene" Binding="{Binding CODBASE}" IsReadOnly="True" Width="Auto"></DataGridTextColumn>
        <DataGridTextColumn Header="SubCod" Binding="{Binding SUBCOD_LIST}" IsReadOnly="True" Width="Auto"></DataGridTextColumn>
        <DataGridTextColumn Header="Stato" Binding="{Binding STATOBENE}" IsReadOnly="True" Width="Auto"></DataGridTextColumn>
        <DataGridTextColumn Header="Descrizione" Binding="{Binding DESCBENE}" IsReadOnly="True" Width="*"></DataGridTextColumn>
    </DataGrid.Columns>
    <Behaviors:Interaction.Triggers>
        <Behaviors:EventTrigger EventName="SelectionChanged">
            <Behaviors:InvokeCommandAction Command="{Binding dgBeni_SelectionChangedCommand}" />
        </Behaviors:EventTrigger>
    </Behaviors:Interaction.Triggers>
    <DataGrid.CellStyle>
        <Style TargetType="DataGridCell">
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="{x:Null}" />
                    <Setter Property="BorderBrush" Value="{x:Null}" />
                </Trigger>
                <DataTrigger Binding="{Binding IsLocal}" Value="false">
                    <Setter Property="Foreground" Value="Red"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsLocal}" Value="true">
                    <Setter Property="Foreground" Value="Blue"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

Datagrid binds its ItemSource and SelectedItem properties, plus it binds the SelectionChanged event to a command via Interaction Trigger.

The code behind of the command is pretty simple stuff as well:

public ICommand dgBeni_SelectionChangedCommand => new RelayCommand(async () =>
{
	if (CurrentItem.SelectedBene is null || CurrentItem.SelectedBene.OID <= 0)
		return;

	var LayerBeni = MapView.Active.Map.FindLayers(Constants.LayerNames.Beni).OfType<FeatureLayer>().FirstOrDefault();

	//ZoomTo - IT BREAKS COMMAND BINDING
	await MapView.Active.ZoomToAsync(LayerBeni, CurrentItem.SelectedBene.OID, new TimeSpan(0, 0, 1));

	//Flash
	var selectionDictionary = new Dictionary<MapMember, List<long>>() { { LayerBeni, new List<long>() { CurrentItem.SelectedBene.OID } } };
	SelectionSet selectionToFlash = SelectionSet.FromDictionary(selectionDictionary);
	MapView.Active.FlashFeature(selectionToFlash);
}, () => true);

It is supposed to get the feature identified by the selected row and zoom+flash to it.

This worked just fine in 3.1 but after the upgrade it works only once (for the first selection) and after that the command is not triggered again (unless I close the ProWindow and open it again). It took me a while to understand that the actual thing the "breaks" the event is the call to ZoomToAsync(). If I just omit the line, it works as intended and the event is raised at every selection change.

As I debugged this I managed to find some more details about the bug: 

  • The issue is the same using the synchronous ZoomTo() function as well (removing the async mark from the anonymous function in the RelayCommand and calling ZoomTo() within a QueuedTask() call produces the same result)
  • It seems to affect any event command binding (I also tried to work with some mouse-related events to bypass the issue, but they all break in the same way)

Currently I couldn't find any workaround to bypass this, without changing the UI. The only solution that comes to my mind would be to separate apart selection and zoom+flash operations, for instance exposing a button that triggers the action after row selection on the datagrid: it should work, but I can't change UI in such way so for now I just skip zooming. 

I believe this is an actual bug of the SDK but in case anyone will come across this issue and finds some solution please let me know.

0 Kudos
1 Solution

Accepted Solutions
GKmieliauskas
Esri Regular Contributor

Hi,

Your datagrid behavior implementation is redundant. You can do the same workflow in your datagrid SelectedItem binded property setter:

 

 

private object _selectedBene;
public object SelectedBene
{
  get { return _selectedBene; }
  set { 
    _selectedBene = value;
    OnPropertyChanged();
    _ = YourWorkflow();
  }
}

private async Task YourWorkflow()
{
	var LayerBeni = MapView.Active.Map.FindLayers(Constants.LayerNames.Beni).OfType<FeatureLayer>().FirstOrDefault();

	//ZoomTo - IT BREAKS COMMAND BINDING
	await MapView.Active.ZoomToAsync(LayerBeni, SelectedBene.OID, new TimeSpan(0, 0, 1));

	//Flash
	var selectionDictionary = new Dictionary<MapMember, List<long>>() { { LayerBeni, new List<long>() { SelectedBene.OID } } };
	SelectionSet selectionToFlash = SelectionSet.FromDictionary(selectionDictionary);
	MapView.Active.FlashFeature(selectionToFlash);

}

 

Sorry, I don't know your real structure and object types. So I improvised

 

View solution in original post

0 Kudos
4 Replies
GKmieliauskas
Esri Regular Contributor

Hi,

Your datagrid behavior implementation is redundant. You can do the same workflow in your datagrid SelectedItem binded property setter:

 

 

private object _selectedBene;
public object SelectedBene
{
  get { return _selectedBene; }
  set { 
    _selectedBene = value;
    OnPropertyChanged();
    _ = YourWorkflow();
  }
}

private async Task YourWorkflow()
{
	var LayerBeni = MapView.Active.Map.FindLayers(Constants.LayerNames.Beni).OfType<FeatureLayer>().FirstOrDefault();

	//ZoomTo - IT BREAKS COMMAND BINDING
	await MapView.Active.ZoomToAsync(LayerBeni, SelectedBene.OID, new TimeSpan(0, 0, 1));

	//Flash
	var selectionDictionary = new Dictionary<MapMember, List<long>>() { { LayerBeni, new List<long>() { SelectedBene.OID } } };
	SelectionSet selectionToFlash = SelectionSet.FromDictionary(selectionDictionary);
	MapView.Active.FlashFeature(selectionToFlash);

}

 

Sorry, I don't know your real structure and object types. So I improvised

 

0 Kudos
Asimov
by
New Contributor III

Hi @GKmieliauskas

Thanks for your reply.

I cannot test your suggestion right now because some systems are under maintenance for a couple days and I can't run the solution, but I guess it can work and probably bypass the problem.

The issue here is that, in the context of my workflow, the redundancy of the event binding is actually useful: the sample code I posted is a stripped-down simplification of the actual structure, and my ViewModel doesn't directly host SelectedBane property, which is defined on another class. I could implement the action on the setter as you suggested, but it would "force" me to give that class the scope to some other VM variables and concepts which aren't really of its competence, hence I prefer a command in the VM.

My point is: shouldn't the command work as it is defined? Regardless of the redundancy of this specific case, you can always be in a situation where you need to use a command with a call to Zoom() inside it, and it would break the binding (assume, for instance, that you need to implement the zoom on mouse click under certain conditions).

Apart from that, your suggestion can surely be an acceptable workaround for my case, and I will try it as soon as I can run my solution again, will let you know.

0 Kudos
GKmieliauskas
Esri Regular Contributor

I think your code goes into race condition. You don't know each part will update first MVVM or code behind on selection changed. If you started using MVVM then you need to avoid writing code in code behind.

0 Kudos
Asimov
by
New Contributor III

Hi @GKmieliauskas, I'm not sure I'm following you: there is no code at all on the code behind, the event is handled through a command (it is NOT an event handler on code behind) and the code is on the ViewModel class, which is the DataContext of the view. The code is completely MVVM style and I didn't initially put the SelectionChanged code on the SelectedItem property setter only due to separation purposes (the binding property is defined on another class, in the VM I have an instance of this class which I use for some bindings).

As far as I am concerned, binding UI events to commands is a legit way to handle actions, and I cannot see why it should go into race condition nor cause any issue: when the command is triggered, SelectedItem has already been set to the new value, I can see this 100% of the times. Plus, as I wrote in my previous messages, this workflow worked with no issues with 3.1 and it still works with 3.2 if I just omit the call to the zoom function: that's why I suspect there is something more going on here.

Anyway removing the command and putting the code on the setter does the trick, so I'm marking your answer as a solution and I thank you again for your support.

0 Kudos