Improve performance of loading images into ComboBox - wpf

I am using WPF ComboBox to populate a list of countries as following:
Inside ComboBox I have a VirtualizingStackPanel that contains an image and a TextBlock:
<ComboBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
<Image Width="30" Height="30" Margin="0" Source="{Binding code, Converter={StaticResource ImageComboBoxConverter}}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Text="{Binding country_Text}"/>
</VirtualizingStackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
I am using a simple converter for DataBinding to retrieve my flag files.
The problem is, the list takes around good 3-4 seconds to populate. Can I apply some kind of buffering or caching technique for images to load faster?

You need to virtualize the ComboBox.ItemsPanel, not the .ItemTemplate. The ItemsPanel is the panel used to display all controls, while the ItemTemplate is used to display each control.
All the Virtualized StackPanel does is not render items which are not visible. Instead, it only renders visible items (plus a few extras for a scroll buffer), and will replace the data behind each item as you scroll. So using it to display each individual item is useless, and you need to instead use it as the panel to display all items.
Your code should probably look something like this :
<ComboBox ItemsSource="{Binding Countries}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="30" Height="30" Margin="0" Source="{Binding code, Converter={StaticResource ImageComboBoxConverter}}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Text="{Binding country_Text}"/>
</StackPanel >
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You may also need to add one or all of these properties to the <ComboBox> tag too.
<ComboBox VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True" ...>

Related

Displaying dynamic data in an ItemsControl as a responsive grid

I'm writing an app that'll allow me to monitor the operational status of websites that I host for my business and clients to maintain a decent uptime on the sites.
I've got most of the app done, but I've hit a bit of a layout snag in rendering the status for each site on the UI which I'm doing with an ItemsControl:
<ItemsControl ItemsSource="{Binding Sites, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<materialDesign:Card Margin="5" Width="220" Padding="5">
<StackPanel>
<TextBlock HorizontalAlignment="Center"
FontSize="20"
Style="{DynamicResource MaterialDesignHeadlineTextBlock}"
Text="{Binding Name}"
TextWrapping="WrapWithOverflow"/>
<materialDesign:PackIcon
HorizontalAlignment="Center" Kind="Web"
Visibility="{Binding OkayIconVisibility}"
Height="50" Width="50" />
<materialDesign:PackIcon
HorizontalAlignment="Center" Kind="Alert"
Visibility="{Binding AlertIconVisibility}"
Height="50" Width="50" />
<TextBlock HorizontalAlignment="Center"
Text="{Binding Status}"
TextWrapping="WrapWithOverflow"/>
</StackPanel>
</materialDesign:Card>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This works except that it only stacks them on top of each other.
What I'd prefer is a horizontal stack that wraps with overflow - so to speak.
The Window is MinWidth="450" so there could be at least 2 sites listed on a row and sites 3 and 4 go beneath them.
I've seen a few posts on creating a custom ItemsControl or setting up a grid as a template for the ItemsControl but those mostly work with a fixed number of items which makes it easier.
I'm very much at a loss as to how I should proceed here. If I could set up a container for the items within the ItemsControl that would suit me perfectly because I can at least work with that. As it is, I don't see how to go about this.
You can use WrapPanel as ItemsControl.ItemsPanel like this:
<ItemsControl ItemsSource="{Binding Sites, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
...
</ItemsControl>
This will stack your Site items horizontally and wraps items when it overflows as you prefer it.

WPF TreeView selection over entire HierarchyItem

I have a TreeView bound to an MVVM observable collection.
My item template is composed by an image and a textblock like the following code show:
<HierarchicalDataTemplate x:Key="TreeViewItemTemplate" ItemsSource="{Binding Items, Mode=OneWay, NotifyOnSourceUpdated=True}">
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image
Margin="-20,0,5,0"
Source="{Binding Icon, Converter={StaticResource TreeViewIconConverter}, Mode=OneWay}"
Style="{DynamicResource SmallIcon}"/>
<Label Content="{Binding Label}"/>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
</HierarchicalDataTemplate>
The problem happens when you Click over an item. If the Mouse cursor is over the StackPanel, the selection will not happen.
I have included also a screenshot to make this more clear.
Of course this happens because the StackPanel is now over the selection area.
Is there any workaround?
I found the answer by myself.
When you customize the TreeView using an hierarchical data template then you should not replicate the TreeViewItem.Header template because at runtime WPF will create one for you.
So, in order to have a custom TreeViewItem this code is enough:
<HierarchicalDataTemplate x:Key="TreeViewItemTemplate" ItemsSource="{Binding Items, Mode=OneWay, NotifyOnSourceUpdated=True}">
<StackPanel Orientation="Horizontal">
<Image
Margin="0,0,5,0"
Source="{Binding Icon, Converter={StaticResource TreeViewIconConverter}, Mode=OneWay}"
Style="{DynamicResource SmallIcon}"/>
<Label Content="{Binding Label}"/>
</StackPanel>
</HierarchicalDataTemplate>

What XAML control should I use to keep all child elements within its borders in WPF?

I'm having a UI design issue, this is what I want the contents of my app window to shrink to fit the smallest dimension of the window.
Using the XAML below, when the window is too narrow the contents are shrunk to fit. Perfect. My problem is when the window is too short, the contents fall out the bottom, like this:
<ItemsControl ItemsSource="{Binding GroupStatsDisplayList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Margin="4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="Auto" Height="Auto" >
<ListBox>
<StackPanel Orientation="Horizontal" Margin="5" Background="Blue">
<Label Content="{Binding GroupID}" FontWeight="Bold"/>
<Label Content="{Binding GroupName}" Width="100" FontWeight="Bold" />
<Label Content="{Binding CallsInQueue}" FontWeight="Bold" />
<Label Content="{Binding TSF}" FontWeight="Bold" />
</StackPanel>
</ListBox>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have read in answers to similar questions that the ItemsControl uses a StackPanel as its default itemspanel and that the StackPanel does behave the way I'm seeing. A grid has been recommended to overcome issues like mine.
So I tried telling my ItemsControl to use a Grid:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Better because the contents do resize when constrained in either direction. Worse because it seems like there is only one ViewBox->ListBox element that gets updated with the last item in my collection (I can see the three items in the collection cycle through the display as the app starts up). I don't see all items in my collection on screen, only the last one.
I also read that a DockPanel could save me...
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
That's when things got crazy and I decided to write this question. Now all items in my collection are there, but they appear one by one when I expand the window horizontally, each new item appears as the existing ones expand to the vertical extent of the window.
How do I get a layout that looks like the first image but will shrink to fit within the smallest dimension of the window?
Why do you have a ListBox in your DataTemplate, when you have only one hardcoded item the StackPanel? To clarify that: The ItemTemplate defines how you want one item to appear in your items Collection. For Example you could create an ItemTemplate which shows an Album Cover on the left, the Artist Name next to it and on the bottom a Star Rating.
A Grid should not be used as an ItemsPanel, because for that you would need to supply a dynamic Grid/Col Definitions collection.
Using a ViewBox is my best advice. But not in the ItemTemplate, this would only size one children, a viewbox around the whole itemscontrol.
This is the XAML that I used based on the answer from dowhilefor.
<Viewbox Margin="3" >
<ItemsControl ItemsSource="{Binding GroupStatsDisplayList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="1" Background="LightGreen">
<Label Content="{Binding GroupID}" FontWeight="Bold"/>
<Label Content="{Binding GroupName}" Width="100" FontWeight="Bold" />
<Label Content="{Binding CallsInQueue}" FontWeight="Bold" />
<Label Content="{Binding TSF}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
I haven't done WPF in a while, and probably it's not considered very good practice, but maybe you could just use a scaletransform on your entire gui ?
I always liked playing around with them.

WPF Nested Databinding; How Do I Bind To An Item Inside Another Item

I have a ListView bound to an ObservableCollection of CustomerContacts.
It works great so far, but being new to this, I'm not sure how to do the next part.
Each customer contact has several contact types, which I want to display under their name.
So inside of CustomerContacts I have another ObservableCollection of ContactTypes.
Here is my current datatemplate:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />>
</DockPanel>
</DataTemplate>
And here's my first attempt at putting the listview inside:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<ListView ItemsSource="{Binding ContactTypes}">
<ListView.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Margin="3,0,0,0" HorizontalAlignment="Center" Text="{Binding Path=ContactType}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />-->
</DockPanel>
</DataTemplate>
I want to replace the TextBlock bound to Title with a ListView/ListBox/ItemsControl bound to the items in ContactTypes.
Somewhat similar to this question: < WPF: bind to a List inside a class > but without all the codebehind. It would be nice to have it in the XAML.
There's a couple things I would do here:
Simplify what you're trying to do in order to isolate the problem. Instead of that whole ListView business, just put in there an ItemsControl: <ItemsControl ItemsSource="{Binding ContactTypes}" /> That'll give you the default view of things, which may just show you the data type of your objects (whatever ContactTypes is), but it'll let you know if the binding is working. If it is, you'll get something listed there. If it isn't, you won't.
If you're not getting anything listed, go use Snoop to drill into it and look for errors. Snoop shows the databinding errors, allows you to inspect the DataContext of each of the items, etc.
If you're still having problems, it might benefit us if you posted your class definitions and your code where you wire things up. There might be some other underlying issues (for instance, is the ContactTypes property null when the binding initially occurs and you're not using INPC to let the binding system know when it changes?).

WPF ItemsPanelTemplate not working

I'm trying to get an ItemsPanelTemplate working for a ListBox. The ListBox is used in a DataTemplate, and none of my implicit ListBox styles override the default visual style. For some reason, the ItemsPanelTemplate I'm specifiying for the control (a WrapPanel) is being ignored and a StackPanel is used instead.
This is the entire template, I can only assume there's something I'm missing which is causing this.
<DataTemplate x:Key="GroupLargeIconsTemplate" DataType="{x:Type Core:IGroup}">
<ListBox ItemsSource="{Binding Children}" OverridesDefaultStyle="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" VerticalAlignment="Top" IsItemsHost="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto"
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<conv:IconConverter x:Key="IconConverter"/>
</DataTemplate.Resources>
<StackPanel Margin="2" Width="100" Height="140">
<Image Source="{Binding Icon,Converter={StaticResource IconConverter},ConverterParameter=96}"/>
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
My guess is that the width property in the WrapPanel is making it behave as a Stackpanel.
Strange, the control template is not supposed to override the explicitly specified values, rather the opposite scenario comes to my mind...
That is, unless the control template provides a panel with "IsItemsHost" set to true. Then the ItemsPanel is ignored. Which, probably, is your case.

Resources