How to show busy indicator for a WPF ItemsControl? - wpf

I have an ItemsControl where I'm binding the ItemsSource property to my ViewModel. It takes a few moments for the DataTemplate to render. I would like to display a "busy indicator" while the DataTemplate renders.
<ItemsControl ItemsSource="{Binding Request.RequestDiscussions}">
Can this be done with DataTriggers or can it be done by the use of the ItemsControl's events? Basically, I need an event when the binding begins and when the rendering has completed.

I wrap the item I want to have a busy indicator in a grid and add the indicator as a sibling that is centered horizontally and vertically. This lets them overlap nicely. Then I bind the visibility of the indicator to an IsBusy property on my viewmodel letting the bindings take care of everything.

Related

Refresh Scroll position of WPF ComboxBox on ItemSource's reload

I have a ComboBox which uses ListBox to show items. ListBox’s ItemSource is bound with a CollectionViewSource.
Issue: Once I open ComboBox and scroll through the items and leave it in middle or at bottom. Once I reopen ComboBox, even though I refresh or reload ItemSource (CollectionViewSource), the Scrollbar remains at the same place where I left it last time. I want it to be as default (at top) each and every time I reload ItemSource.
Is there is any way of doing this in XAML itself? Another thing, I cannot use Behavior or Attach property. I want any template or style for this.
In order for a Style on a ListBox to embody some behavior for the <ScrollViewer>, you would need to use an Attached Property / Attached Behavior to control the ScrollViewer's "grabber" position. This is because your collection is bound to your ListBox and notifying when it is updated needs to drive a behavior that isn't natively on the ListBox. It may be possible to reset the scroll position with a <ControlTemplate> for the <ScrollViewer> itself, but I imagine it would be difficult as it would likely involve manipulating Transforms / StoryBoards based on DataTriggering with your ItemsSource, but again that may cause a dependency on needing to use an Attached property, which for some reason you can't use...
If you simply want to get a result now, and you don't care about testability or re-usability, I would handle the TargetUpdated event in the code-behind. It's ultimately what the Attached Behavior would end up doing. On the other hand, if you do care about re-usability then you need to evaluate and challenge why you can't use an Attached Behavior (they are also testable, too); an Attached Behavior would be also easier than trying to edit a ControlTemplate.
Here is the code-behind approach:
.xaml:
<ListBox x:Name="myListBox"
ItemsSource="{Binding MyItemsSource, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
.xaml.cs:
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
if (myListBox.Items.Count > 0)
myListBox.ScrollIntoView(myListBox.Items[0]);
}
Edit: On the flip-side, if you are using MVVM, you can do something like this SO post suggests and set IsSynchronizationWithCurrentItem="True" and when you refresh your ItemsSource, simply set your SelectedItem to the first in the list and handle the SelectionChanged event in your vm.

Combobox Text property not changing in user control

I have a list box that has a list of user controls. Each user control has 5 combo boxes. I want to be able to read the selected text of each combo box in each user control from the main application. However, when I change the selection in the combo box, the text property of the combo box in the user control doesn't change when I read it in the main application.
Code-behind:
radQueryParamList.Items.Add(new TCardQueryParameters());
Xaml (This is just a data template for how to display a TCardQueryParameters object):
<DataTemplate x:Key="TCardViewQueryParamDataTemplate">
<tcardqueryparam:TCardQueryParameters x:Name="TCardViewerParamUC" />
</DataTemplate>
<telerik:RadListBox Grid.Column="1" ItemTemplate="{StaticResource TCardViewQueryParamDataTemplate}" Name="radQueryParamList" VerticalAlignment="Top" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.ColumnSpan="3">
Where I loop over the list of user controls
string test = radTESTGACC.Text;//TEST combo box, Text property changes
//radQueryParamList is a listbox of user controls where TCardQueryParameters is the UC
foreach(TCardQueryParameters param in radQueryParamList.Items)
{
//Each UC has a radGACC combo box in it, and I am reading what the user
//selected for each user control here in the main app, but the text property
//never changes
String gacc = param.radGACC.Text; //Text property DOESN'T CHANGE
}
I thought that each instance of the user control would keep its own state and I would just be able to read what the user selected for that combo box, but that doesn't seem to be the case.
You have not bound the SelectedItem, SelectedValue, or SelectedIndex property of your internal ComboBox to anything so it maintains its selection.
An ItemTemplate is like a cookie cutter. It contains the definition of the object, but not the object itself. Properties specific to the object's state are lost unless they are bound to something on the DataContext behind the template.
This is important to note for two aspects.
First off, to improve performance WPF usually unloads items which are not visible, which often results in items being re-created from their template anytime they are reloaded. An example would be when you minimize an application to the taskbar, then maximize it again. This is usually better on performance and memory usage, however it does mean you have to be sure you store the state of items that were created with a Template somewhere.
And second, by default ListBoxes use something called virtualization. A simple way of explaining this would be this:
Suppose you have a ListBox of 100,000 items. In your ListBox, only 10 items can be visible at a time. WPF will render roughly 14 items (the 10 visible ones, and then a few extra for a scroll buffer so you don't see anything unusual while scrolling). When you scroll to new items, WPF just re-uses the existing items that are already rendered, and just replaces the DataContext behind those items.
As you can guess, it is far better on performance to render 14 UI items instead of 100k items.
So to answer your question, you will probably want to bind either SelectedItem, SelectedValue, or SelectedIndex of your TCardQueryParameters UserControl to a property on the DataContext (which in your case appears to be another different UserControl).
It should probably be noted that what you are essentially doing is creating a list of UserControls, assigning them to the ListBox, and then telling the ListBox that it should draw each UserControl with another separate UserControl. So although you are changing the SelectedItem in the template UserControl, that change is not being reflected to your ListBox.Items copy of the UserControl.
I'm guessing you probably don't want that, so would recommend removing your ItemTemplate completely.
Or better yet, create a new class object containing all the data for your UserControl, and adding that to your ListBox.Items. Then tell the ListBox to draw that item using a TCardQueryParameters UserControl as the ItemTemplate, like you have now.

Data virtualization in a WPF listbox

I have a scenario where in I populate a listbox with a 1000 items. I set the ItemsSource property with a source of data.
I have a requirement where I need to strike out an item of the listbox based on certain criteria, when the UI loads. I am using styles + attached properties to achieve the same by setting ContentTemplate of ListBoxItem in the callback method of attached property.
My problem is when I try to generate a ListBoxItem using ItemContainerGenerator.ContainerFromItem, for an item which is at the end of the list, I get null. As a result, I cannot strike out items of the listbox which are at the bottom of the list.
Has it got something to do with virtualization. I want to obtain all those ListBoxItems on load.
Is there any workaround?
Thanks
This is definitely caused by virtualization. This is exactly what UI virtualization is supposed to do - only create ListBoxItem objects for items that are visible on the screen. You can easily see that this is indeed the cause by setting VirtualizingStackPanel.IsVirtualizing = false on your ListBox and see that ItemContainerGenerator.ContainerFromItem no longer returns null.
You can set a style for your ListBoxItems in your ListBox that will have the logic to strike out the items as needed. This should also work when virtualization is enabled. For example:
<ListBox>
<ListBox.Resources>
<Style TargetType=ListBoxItem>
...
</Style>
</ListBox.Resources>
</ListBox>

WPF - databinding label on listbox ismouseover

I'm still new to WPF, and I'm trying to do something that's beyond my knowledge at the moment.
I have a listbox databinded to the source collection, and a label. I'd like to bind the label's Content value to the listbox's item over which is mouse hovered.
Say I have DataTemplate binded to the class MenuItem:
<DataTemplate DataType="{x:Type local:MenuItem}" x:Key="MenuListTemplate">
Which has member Text. I want my Label to display Text from element which is mouse overed in list. I have the IsMouseOver trigger for my textbox, but have no idea how to bind Label.Content to it.
Any tips?
I don't think that binding can achieve your goal with ease. I think it's easier to do with routed events.
Subscribe to the MouseMove event at the ListBox level. Check if the source of the event is a ListBoxItem, and if it is use this item to update the label.

Where is IsSynchronizedWithCurrentItem property (or equivalent) for a TreeView?

Tell me it ain't so.
I have a typical windows/file explorer like setup.
Left Side I have a TreeView all data bound showing nodes in a hierachy
Right Side I have a ListView showing Node.Properties
ListView has a IsSynchronizedWithCurrentItem property, which rocks. e.g. If I had another ListView showing a list of nodes and both listViews have this property set to true. Changing selection of node in NodesListView will update the PropertiesListView automatically.
Now I need the same thing with a NodesTreeView and a PropertiesListView... and seems like TreeView has no such property.
Is there a more 'the WPF way' kind of solution to this problem ? Or do I have to handle the NodeSelectionChanged event of the Tree and refresh the listView via code.
A really simple solution is to bind your "details" UI elements to the SelectedValue property of the TreeView. For example, if your TreeView looked like this:
<TreeView Name="CategoryName" ItemsSource="{Binding Source={StaticResource A_Collection}, Path=RootItems}" />
Then you could bind details UI elements (like a textbox) using:
<TextBox Text="{Binding ElementName=CategoryTreeView, Path=SelectedValue.Name}"/>
Would cause the text box to be bound to Name property of the items currently selected in the TreeView.
If you want to bind many UI items as details for the selected TreeView item, consider setting up a DataContext on the elemtent that contains all the details controls (DockPanel / Grid / StackPanel, etc).
<ListView Name="listView1"
ItemsSource="{Binding Path=SelectedItem.Modules,
ElementName=treeView1, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True">
Where ".Modules" is the collection of child items off the selected treeview item you want to display. Don't worry about wiring up the "SelectedItemChanged" event on the treeview.
Why exactly it doesn't implement the property, I do not know, but i have a suggestion down below.
Your code above will work, however, it is not what the IsSynchronizedWithCurrentItem property does. Any ItemsControl binds to the ICollectionView of the ItemsSource property. To get that ICollectionView, we can call CollectionViewSource.GetDefaultCollectionView(object o). Depending on the type of object o, you get a different concrete implementation of the ICollectionView inteface. CollectionView and ListCollectionView are two concrete classes that come to mind.
The ICollectionView interface contains a member called CurrentItem. What the IsSynchronizedWithCurrentItem does is: whenever an item is clicked on the ItemsControl, it sets the CurrentItem for the collection view. The ICollectionView also has two events: CurrentItemChanging and CurrentItemChanged. When the IsSynchronizedWithCurrentItem property is set, the ItemsControl will update the SelectedItem based on what the ICollectionView's CurrentItem is. Makes sense?
In master/detail WPF scenarios, we simply are binding to ICollectionViews and their CurrentItem (the CurrentItem syntax is something like {Binding Items/Name}, where Name is the Name property on the collection's CurrentItem.
So although your code works for your purposes, it doesn't do what that property does. To do what the property does, you need to do the following:
When an item is selected, you need to figure out which collection it belongs to. How do we do this? I believe this is why TreeView doesn't implement it. The selected item is displayed in a TreeViewItem. The DataContext is the object itself, but what is the parent collection ? I guess to get it you could either cache it in some hashmap (silly, but will work) or you can walk up the logical tree and get the TreeViewItem's parent that happens to be an ItemsControl. The ItemsSource property will be your collection.
Get the ICollectionView for that collection.
Need to cast that ICollectionView into a CollectionView (ICollectionView doesn't implement CurrentItem setter)
Call SetCurrent(.. , ..) on the CollectionView instance
Now, anything that is bound to that ICollectionView's CurrentItem will be updated.
This became longer than I expected. Let me know if any further clarification is necesary.
My solution to this turned out to be pretty tiny.. don't know if it is equivalent to IsSynchronizedWithCurrentItem. ListView refreshes as expected.
// the XAML
<TreeView DockPanel.Dock="Left" x:Name="tvwNodes" ItemsSource="{Binding}" SelectedItemChanged="OnNewNodeSelected"/>
<ListView x:Name="lvwProperties" ItemsSource="{Binding Path=Properties}"
// the code-behind
private void OnNewNodeSelected(object sender, RoutedPropertyChangedEventArgs<object> e)
{
lvwProperties.DataContext = tvwNodes.SelectedItem; // this returns the selected Node obj
}

Resources