1) For the responsiveness of the DataGrid
, this binding property might help: IsAsync=True
<DataGrid ItemsSource="{Binding MyCollection, IsAsync=True}"
also look into these DataGrid properties:
VirtualizingPanel.IsVirtualizing
VirtualizingPanel.VirtualizationMode (you'll probably need Recycling)
VirtualizingPanel.IsVirtualizingWhenGrouping
EnableRowVirtualization
EnableColumnVirtualization
But be careful, virtualization can play tricks on you. For example, I had a RowHeader (with the row number) and the values got scrambled when virtualization was on.
2) About the async setter for data binding: I was using a custom version of IAsyncCommand
(see Stephen Cleary's example).
I used the command in 2 ways: a) binding to it from the view (avoiding the async setter altogether) or b) launching it from the setter (not nice).
Example: I created an UpdateCommand
as an AsyncCommand
and placed everything I needed done asynchronously (like getting the values from the DB). Everything in this command is wrapped within a display+hide of a "in progress"-like control - in my case, a transparent cover with a spinner + "please wait...", to prevent other user actions (the "screen" is visible while the task is performed). Stripped down sample:
....
public MainWindowViewModel()
{
UpdateCommand = AsyncCommand.Create(Update); // our own custom implementation of AsyncCommand
}
....
public AsyncCommand UpdateCommand { get; }
internal async Task Update(object arg)
{
await SafeWrapWithWaitingScreenAsync(async () =>
{
var value = (int)arg; // or the ActualPageNumber, if used from a1)
var data = await GetDataFromDb(value).ConfigureAwait(false);
...// fill in MyCollection (which is the DataGrid's ItemsSource) using the data
OnPropertyChanged(nameof(MyCollection));// if still needed
}).ConfigureAwait(false);
}
....
public async Task SafeWrapWithWaitingScreenAsync(Func<Task> action)
{
DisplayWaitingScreen = true; //Visibility of the "Waiting screen" binds to this
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
HandleException(ex); // display/log ex
}
finally
{
DisplayWaitingScreen = false;
}
}
a) Binding to the command from the view and
a1) in the command's body use ActualPageNumber
property instead of the arg
value
or a2) passing a CommandParameter
which binds to the same property as TextBox.Text
does. Example (could be missing something, couse is not the real code):
<TextBox Text="{Binding ActualPageNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
<KeyBinding Key="Enter" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
</TextBox.InputBindings>
</TextBox>
b) Not sure this is right, but before seeing Stephen's approach with NotifyTaskCompletion<TResult>
(which I will probably use in the future), for the setter, I launched the command something like:
private int actualPageNumber;
public int ActualPageNumber
{
get => actualPageNumber;
set
{
actualPageNumber = value;
OnPropertyChanged(); //the sync way
UpdateCommand.Execute(value);
}
}