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

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);
}
}

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

WinRT ListView UI Virtualization and DataTemplateSelector with a lot of templates (10-15)

I need to display ListView with different items (in total 10-15 item types). For this I use DataTemplateSelector. But this causes ListView odd behavior during scrolling: at some point it jumps to the top of list view. I've found this article for UWP: https://msdn.microsoft.com/en-us/windows/uwp/debug-test-perf/optimize-gridview-and-listview. It says that ItemTemplateSelector supports only 5 DataTemplates
Additionally, an item template selector only considers five possible candidates when evaluating whether a particular container can be reused for the current data item.
I think this is the reason. I've tried to reduce the number of DataTemplates returned by my DataTemplateSelector and it solved the issue: scrolling works as expected. But how can I solve this issue without reducing number of DataTemplates? I know that I can disable virtualization, but I would like to keep it enabled if possible.
For UWP there is an option to use ChoosingItemContainer event, but it isn't available for WinRT.
Is it possible to solve this issue without disabling UI virtualization in WinRT?
This is what I ended up doing in my project (I have a listview with infinite scrolling). Basically, I did a part of virtualization myself.
I removed DataTemplateSelector completely. Instead, I use one template for all the items:
<ListView
...
>
<ListView.ItemTemplate>
<DataTemplate>
<messages:MyCustomContainer />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Where MyCustomContainer is a simple UserControl:
<UserControl
x:Class="MyCustomContainer"
...
DataContextChanged="OnDataContextChanged"
>
<Grid x:Name="Container"/>
</UserControl>
I instantiate and select appropriate nested template in code behind of MyCustomContainer:
void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
var context = DataContext as MyModelThatHelpsDecideOnAppropriateVisualTemplate;
if (context == null) {
// this means, item has been removed from the list and cached (we call this 'virtualization')
Container.Children.Remove(CurrentTemplate);
ReleaseTemplate(CurrentTemplate); // clear and cache our concrete template
CurrentTemplate = null;
} else {
// this means, we need to get a concrete template
// ... some logic to decide on the proper visual template type
Type templateType = GetTemplateTypeForData(context);
// ... some logic to get visual template from cache
CurrentTemplate = GetTemplateFromCache(templateType);
Container.Children.Add(CurrentTemplate);
}
}
On the bright side, this works fine (does for me, and I have around a dozen item templates).
On the other side, this way the UI framework only virtualizes MyCustomContainer list items, and you have to cache concrete visuals yourself. In my example, you have to store instances of your 10-15 templates in some cache, and implement GetTemplateTypeForData(), GetTemplateFromCache() and ReleaseTemplate()... But that should be really straightforward, took around 100 lines of code for me.

Binding WPF DataTemplate to Dictionary <int, Page>

So I'm trying to bind a XAML DataTemplate to a Dictionary.
Example:
pages: {
1: {
thumb: "https://www.sample.com",
image: "https://www.sample.com"
},
2: {
thumb: "https://www.sample.com",
image: "https://www.sample.com"
}
}
And so on.
Basically, I'm getting a variable number of pages for each request, and I solved the dynamic deserialization by creating a Dictionary with the key/values , but I can't seem to find a way to bind said Dictionary to a XAML DataTemplate.
In this particular view, I am trying to display a grid of the 'thumb' images from a given Content item. Any ideas on how I'm supposed to approach this? Preferably something that can be easily done with ItemsSource or Binding, or such, if there is such a way.
Alright, so I think I found a work-around.
The images are bound to an Image within a DataTemplate in the ListBox I have set up.
I was looking for a way to have one ListBox with a WrapPanel, which would display images from a dictionary - in this case, the thumbs of each page within the dictionary, of which has a variable number of <int> Keys.
While I would like to know if it is possible to bind the Image Source to a dictionary like I have described above, I found I can use this for the time being:
private static void GenerateThumbsAndImages()
{
foreach (Page i in CurrentContentInfo.Pages.Values)
{
PageImages.Add(new Uri(i.image));
PageThumbs.Add(new Uri(i.thumb));
}
}
Which I then bound with my XAML:
<ListBox x:Name="ThumbView" ItemsSource="{x:Static local:Posts.PageThumbs}" ... >
<Image x:Name="PageThumbnailImage" Source="{Binding OriginalString}" ... />
If anybody knows how I could bind directly to the dictionary, that'd be great, but this works fine for now.

WPF and MVVM: bind UserControl to XAML dynamically

seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.

Inherit from ListBox

I'm using several lists across my project instead of trees - for proper virtualization (a lot of items in tree structure).
Those lists are pretty much the same. The only difference is in DataTemplates. Those lists have a few events bound, which I have to copy & update in several places. Current events are used to:
prevent horizontal auto-scrolling
support for arrow keys to navigate through tree structure
I found no way to bind events in a single style in resource dictionary, as events must belong to specific class. So I have to copy exactly same events between classes and bind them to specific lists. That is quite a lot of text, both in XAML and code.
What I wanted to do is to define a new user control, deriving EVERYTHING from standart ListBox, but overriding a few minor methods (instead of events). And reuse this control everywhere where I need such a list without having to copy all the events.
Problem is - it requires me to define custom <UserControl ... />. Is there a way to just use ListBox template/style there? I need no GUI modifications from standart ListBox.
I could be missing some simple way to perform what I want. I'd appreciate any way to do this.
Not sure about your setup but you will probably have to override the ListBox and ListBoxItem. Then override some methods :
public partial class MyListBox: ListBox
{
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new MyListBoxItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyListBoxItem;
}
}
public class MyListBoxItem : ListBoxItem
{
}
This will force your containers to be ListBoxItem overrides.
Now you just have to implement yous specific code tof keys in ListBoxItem overrides. If you don't need any style changes the default ListBox style will be applied.
Now you can use it in your XAML:
<local:MyListBox ItemsSource="{Binding Items}">
<local:MyListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding id}"/>
</DataTemplate>
</local:MyListBox.ItemTemplate>
</local:MyListBox>

Categories