Feedback when loading data asynchronously - c#

I have a ComboBox whose ItemsSource is bound to an ObjectDataProvider that has its IsAsynchronous property set to true. Inside the method that loads the data , I put a Wait for 10 seconds, to simulate a long loading time for this data.
The Asynchronous loading works great - the entire window is still responsive, and after 10 seconds I see the ComboBox dropdown populated.
I would like to alert the user that this specific ComboBox is loading data, during that 10 second wait time. Something like a progressBar in the background of the control, that is enabled only while a certain 'isLoading' property or whatever is set to true. Is it possible to accomplish this?
Thanks.

this looks like the Priority Binding could be a solution for you
<ListBox>
<ListBox.ItemsSource>
<PriorityBinding>
<!-- highest priority sources are first in the list -->
<Binding Path="LongLoadingCollection" IsAsync="True" />
<!-- this contains only one item like "loading data..." -->
<Binding Path="LoadMessage" IsAsync="True" />
</PriorityBinding>
</ListBox.ItemsSource>
</ListBox>
here is an good tutorial for Priority Bindings
http://www.switchonthecode.com/tutorials/wpf-tutorial-priority-bindings
or take a look at msdn
http://msdn.microsoft.com/en-us/library/system.windows.data.prioritybinding.aspx
hope this helps

It doesn't appear that the ObjectDataProvider has any properties to say when it is and isn't retrieving data.
It depends on your architecture, but you could expose properties that give a "load state" to your object that contains the method that loads the data. Then you could bind a progress bar or other "Please Wait..." kind of UI to that new state property.

Related

How to do a clean implementation to update items within an UWP ListView from a background thread?

I am currently building an UWP application which should control several jobs, where each job could be updated from a background thread. Currently, I am using an ObservableCollection in a static "Core-Class" in which all jobs are stored (see question 1). This list is bound using a property in the view model of my view:
public ObservableCollection<JobBase> Jobs
{
get
{
return Core.Jobs;
}
set
{
Core.Jobs = value;
}
}
The list view's ItemsSource is bound to this property in my view model. I am using a custom ListView.ItemTemplate which uses a DataTemplate linked to my Job-class to display the information for each job:
<ListView
x:Name="JobsListView"
Grid.Row="1"
ItemsSource="{x:Bind ViewModel.Jobs, Mode=OneWay}"
SelectionMode="Single"
IsItemClickEnabled="True"
SelectedItem="{x:Bind ViewModel.RunningJob, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="lib:JobBase">
<Grid Margin="0,12,0,12" Height="52">
[...]
<TextBlock FontSize="12" Text="{x:Bind Progress, Mode=OneWay}" Margin="4,0,0,0" />
[...]
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Some thoughts about this construct:
1) I think using an ObservableCollection in my static "background" Core-Class is not beautiful. I could change this to a normal List and do a cast to an ObservableCollection in the property of my list view. However, I will need some events when new jobs are added (or old ones deleted) in order to update my ObservableCollection which is bound the to the ListView. Is this the preferred implementation?
2) As soon as the progress of a certain job is updated (from within a background thread), I cannot show those updates in the list view. Even if I implement events for this, I have no clue how to update the binding of a certain item within my list view?
3) I could implement the INotifyPropertyChanged interface in my Job class, but I think this would not be a beautiful implementation either. In addition, I am raising exceptions as I cannot update my GUI from a background thread?
As you see, I am looking for a "professional" implementation on this issue. Therefore, I would prefer a solution which uses bindings and avoids a complete reloading of the whole job list if only a single item is updated.
I am really looking forward for your implementation tips :-)
You should not be replacing the instance of the object being bound to ItemsSource. Instead, you'll need to modify the contents of the list. You can replace the ItemsSource if you choose, but this can lead to serious heap fragmentation and performance problems.
To update the UI thread, use CoreDispatcher.RunAsync to get back on the UI thread from a thread that could potentially be in the background https://learn.microsoft.com/en-us/uwp/api/windows.ui.core.coredispatcher.runasync?view=winrt-18362
await _coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Bindings.Update();
});
If you need to just update one item at a time, ObservableCollection could be ideal, especially when the List includes objects which implement INotifyPropertyChanged. If instead you prefer to update several objects at once you'd set the binding to OneTime then use Bindings.Update() to update all the bindings in the ListView at once. This documentation can help to provide more info about optimizing your ListView. https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-gridview-and-listview
ObservableCollection isn't necessary. There are other mechanisms of data binding if you prefer.
For more on data bindings, see https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth

Unscrollable list in a scrollable ScrollViewer

I have something like
<ScrollView>
<StackPanel Orientation="Vertical">
<TextBox>
<TextBox>
*** // Tons of TextBoxes
<TextBox>
<RadJumpList> // Just a ListBox from Telerik
</ScrollView>
For now, the whole page is scrollable (due to ScrollViewer) and RadJumpList is also scrollable itself (by definition). How to disable RadJumpList from scrolling?
PS: probably, too many "scroll"s per a question, but i guess its pretty intuitive :)
EDIT: from my experience, this is bad idea. If list has 100+ items, it can delay page loading for a several seconds list would be loaded whole at once. Better idea is to put all TextBoxes to the ListBox via dataTemplateSelector.
If you don't set the height of your RadJumpList and disable scrolling like ScrollViewer.VerticalScrollBarVisibility="Disabled".It will be ok.
Actually, even better way is to push all stuff into the list via ItemTemplateSelector.
Otherwise, if there would be 100+ items, page would load for a several seconds.

Use another element's ItemsSource property in a MultiBinding

I'd like to use the ItemsSource property from a particular element as one of the bindings in another element's MultiBinding. Here's what I have so far:
<Label>
<Label.Content>
<MultiBinding Converter="{converters:myMultiValueConverter}">
<Binding Path="PageIndex" />
<Binding ElementName="anotherElement" Path="ItemsSource"/>
</MultiBinding>
</Label.Content>
</Label>
This works once (when the ItemsSource is initially set), but the binding fails to update when the ObservableCollection bound to the original element's ItemsSource property has items added or removed. Is this kind of binding possible?
Add a dummy binding (- you don't need the value -) like this to force the MultiBinding to be reevaluated:
<Binding ElementName="anotherElement" Path="ItemsSource.Count"/>
Edit: Just noticed a flaw: If you move items that would not register if that does not change the Count property in-between, maybe this is relevant for you. In that case you could bind to your own dummy for which you can fire change notifications upon CollectionChanged (not all that clean in any case though).
You might want to consider HighCore's suggestion, a get-only property that returns the calculated value for which you manually fire PropertyChanged in all places that it depends on is usually quite convenient.

Binding and support for rich text formatting

Consider the following scenario:
I have a ListView that is bound to an ObservableCollection using the DataContext:
<ListView ItemsSource="{Binding}">
The class containing the string data uses the DependencyProperty mechanism to keep the displayed content synced with the data collection.
The ListView has one column that is editable (I followed the tutorial here to achieve this); the ListViewItem is then either a TextBlock or a TextBox. This is done using a DataTemplate and two Style resources.
I'd like to format the string displayed in the TextBlock based on a search string. Specifically, I'd like to format the items of the ListView to become bold as the user types in their search query if there is a match (only the characters that match in sequence should be made bold). This only needs to be displayed for the text currently being rendered using the TextBlock (that is, text not currently being edited).
I've considered using an IMultiValueConverter that takes in a reference to the TextBlock that renders the data so that I can format the text appropriately. However, this will destroy the binding that I've set up:
<TextBlock.Text>
<MultiBinding Converter="{StaticResource searchFormatter}" ConverterParameter="{x:Reference Name=txtSearch}">
<MultiBinding.Bindings>
<Binding Path="NameOfBoundDependencyProperty"/>
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
searchFormatter being the IMultiValueConverter and txtSearch being the TextBox containing the search query.
I'm still learning WPF so I'm not familiar with the best approach or to what's possible. Is there a way to keep the data bound (so that edits reflect in the collection and the ListView) and still represent the data differently to the user (so that search matches may be bold)? Perhaps it would be cleaner if I manage the binding manually?
I decided to use a Control that supports HTML so that I could use an IValueConverter to update the value of the displayed text on the fly without affecting any active bindings. I used the code from here and modified it so that it looked like a TextBlock within my ListView:
BorderBrush = Brushes.Transparent;
SelectionBrush = Brushes.Transparent;
Cursor = Cursors.Arrow;
BorderThickness = new Thickness(0);
Background = Brushes.Transparent;
However, I still needed to trigger the IValueConverter so that the display is updated as the user types in their search query (code from here):
ICollectionView view = CollectionViewSource.GetDefaultView(ItemsSource);
view.Refresh();
I didn't want to slow down the search process so I only forced this refresh if there was actually a match (or if the state of having a match moves to no match). My IValueConvertor simply inserted the bold tags to match the search query:
<RichTextBox Text="{Binding Path=DisplayItem, Converter={StaticResource searchFormatter}, ConverterParameter={x:Reference txtSearch}}"/>
Where searchFormatter this time is an IValueConvertor.

I want an hourglass while my IsAsync binding is busy; possible?

I've currently got XAML code like this:
<ListView Name="fileLV" SelectionMode="Extended" ItemsSource="{Binding path=DataContext.SelectedAsset.Files,ElementName=selectionView,IsAsync=True}"/>
That "Files" property takes fifteen seconds to return. The whole time the user is wondering what's going on. I've seen some other code to show the fallback value or use multiple bindings, but those don't rely imply "leave this control alone" like an hourglass over that control would imply.
What I want is to be able name a binding and then bind some other properties to that binding's IsBusy property. I want a trigger to change the cursor on that listview while his binding is busy. Is there any existing WPF framework help for this?
I don't know of any built-in, out-of-the-box solution but there sure are ways to make a nice experience out of it.
I will give you the quick idea of how I would build this and if you need I can come up with the code as well:
Create a "LoadingItem" DataTemplate that would show an progress bar of some kind as an item of your list
Create a "DataTemplateSelector" to switch between the LoadingItem
and the RegularItem of your list.
In your Files property, clear the collection and add an item that
will be shown as LoadingItem (depends on how you built your
DataTemplateSelector logic. Start another thread to scan for files
and fill a return the results in a temporary collection
(BackgroundWorker). When the method returns, you are on the UI
thread again, clear your ItemsSource collection again and fill it
with the results.
For this do not use IsAsync. On the Property use a BackGroundWorker. First return a source with a "working message", start BackGroundWorker, then on the callback supply the real source and call NotifyPropertyChanged. You can even have a progess bar.
I was able to make the DataTemplateSelector work. One caveat was that all the bindings for the ListView need to be enumerable. In my control I added a resource like this:
<UserControl.Resources>
<x:Array x:Key="LoadingTemplate" Type="DataTemplate">
<DataTemplate>...my daisy code...</DataTemplate></x:Array>...
Then I changed my binding to look like this:
<ListView.ItemsSource>
<PriorityBinding>
<Binding Path="DataContext.SelectedAsset.Files" ElementName="selectionView" IsAsync="True"/>
<Binding Source="{StaticResource LoadingTemplate}" />
</PriorityBinding>
</ListView.ItemsSource>
Then I installed this template selector:
public class OverridableDataTemplateSelector: DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return item as DataTemplate ?? base.SelectTemplate(item, container);
}
}

Categories