We are seeing some strange behavior surrounding the SelectedItem
property in our DataGrid
. Some background information:
The DataGrid
displays the result of a query to our database.
There is a button that allows the user to manually refresh the results in the DataGrid
. There is an auto-refresh mechanism whereby the results will automatically refresh every 30 seconds.
What we are seeing is the SelectedItem
property will always become index 0 of the ItemsSource for the Datagrid when the auto-refresh occurs. But we want the currently selected row to remain the selected row after the refresh. However, if the user manually clicks refresh, the selected row remains the same after the refresh which is strange because the same code is running for the refresh logic. And yes, we have code that remembers the currently selected item which then gets set again after the refresh has been completed.
Here is some of the relevant code:
<UserControl.Resources>
<CollectionViewSource Source="{Binding DataGridResults}" x:Key="ReferralItemsSource"/>
</UserControl.Resources>
<customControls:CustomDataGrid x:Name="GridControl"
ItemsSource="{Binding Source={StaticResource ReferralItemsSource}}"
SelectedItem="{Binding DataContext.SelectedReferral, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
IsReadOnly="False"
IsSynchronizedWithCurrentItem="True"
SelectionMode="Single">
private async void RefreshWorklist(bool invokedByAutoRefresh = false)
{
try
{
if (Initialising || ShowSpinner || IsProcessing || ShowRefreshSpinner || IsCurrentWorklistDeleted || !_sessionData.IsActive()) return;
IsProcessing = true;
RefreshWorklistCommand.RaiseCanExecuteChanged();
if (CurrentWorklistId != null)
{
var selectedReferralId = SelectedReferral.pk_Referral_ID;
if (invokedByAutoRefresh)
{
// Refresh has been invoked by _timer, so show spinner on the results page only
ShowRefreshSpinner = true;
}
else
{
// User has manually clicked refresh button so show app wide spinner
ShowSpinner = true;
if (_timer != null)
{
SetupWorklistRefreshTimer(); // Setup _timer again so that it will refresh again at an appropriate time
}
}
Referrals = await _referralRepository.GetReferralsFromWorklistAsync(CurrentWorklistId.Value, invokedByAutoRefresh);
if (Filters.Count > 0)
{
var listOfReferralPks = ReferralFiltering.GetFilteredResults(Referrals, Filters.Where(f => f.HasBeenApplied).ToList());
var filteredResults = Referrals.Where(r => listOfReferralPks.Contains(r.pk_Referral_ID)).ToList();
DataGridResults = MapReferralLookupItemsToReferralLookupItemViewModels(filteredResults);
}
else
{
DataGridResults = MapReferralLookupItemsToReferralLookupItemViewModels(Referrals);
}
SelectedReferral = DataGridResults.FirstOrDefault(r => r.pk_Referral_ID == selectedReferralId);
}
}
catch (Exception e)
{
_errorHandler.DisplayError(e);
}
}
As explained earlier, RefreshWorklist()
is called by the manual refresh invoked through a Command
:
private void Execute_RefreshWorklist()
{
RefreshWorklist();
}
Or automatically through the use of a Timer
:
private void SetupWorklistRefreshTimer()
{
_timer?.Dispose();
var refreshInterval = _userSettingsRepository.GetIntegerSystemSetting("ReferralsWorklistRefreshInterval");
if (refreshInterval <= 0) return; // If this is 0 or below then the refresh should be disabled
if (refreshInterval < 10) // If it is less than 10 then set it to 10 to avoid too many MT calls
{
refreshInterval = 10;
}
var timeUntilFirstTick = refreshInterval * 1000;
_timer = new Timer((s) => RefreshWorklist(true), null, timeUntilFirstTick, refreshInterval * 1000);
}
And finally the SelectedItem
property view model binding property:
public ReferralLookupItemViewModel SelectedReferral
{
get { return _selectedReferral; }
set
{
if (_selectedReferral != value)
{
_selectedReferral = value;
OnPropertyChanged();
}
}
}
Does anybody have any idea as to why this behavior is occurring? Is it something to do with the Timer
? I appreciate this is not a simple question so please ask away for more information.
You need to assign properties in Binding
with the UI on the UI thread.
Replace your Timer
with a DispatcherTimer
or use Dispatcher.Invoke
or Dispatcher.BeginInvoke
inside the existing Timer
callback when calling RefreshWorklist
.
By pressing the Button
you are already on the UI thread, but Timer
has its own thread that is different from the UI thread.
DispatcherTimer
callback are called on the UI thread instead https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netframework-4.0