Grouping, virtualization and scrolling in a ListBox - wpf

Using following code ListBox jumps to the next group during scrolling. Since one group shows more items than can fit screen, user never sees all items of the group.
Since I have a lot of items I need viritualization.
Setting CanContentScroll="False" fixes the issue but then virtualization is gone and UI hangs for 20 seconds. Is there a way around this?
<ListBox
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True">
<ListBox.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Type}" />
...
</StackPanel>
</Expander.Header>
<ItemsPresenter />

Try using the ScrollUnit=Pixel property to get smooth scrolling. The default is ScrollUnit=Item which causes the group to scroll as one unit.
<ListBox
VirtualizingPanel.IsVirtualizing='True'
VirtualizingPanel.IsVirtualizingWhenGrouping='True'
VirtualizingPanel.ScrollUnit='Pixel'
/>

Related

How to have multi selection and checkbox work together in telerik WPF

I have check boxs inside my radchombobox and if I select multiple names it shows in the text field but noting happens till I click the check box then the radcombobox closes after one check. How to I combine both actions so if I select the names or check the check boxes it work. Also how do I get the combobox not to close after I check one item. New to telerik WPF thnka you
<Grid>
<telerik:RadComboBox x:Name="radComboBox"
VerticalAlignment="Center"
Margin="0 50 0 0"
Grid.Column="1"
Grid.Row="1"
ItemsSource="{Binding Items}"
AllowMultipleSelection="True">
<telerik:RadComboBox.ItemContainerStyle>
<Style TargetType="{x:Type telerik:RadComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPlanel Orientation "Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
VerticalAlignment="Center"
Width="5"/>
<TextBlock Text="{Binding Name}"/>
<StackPlanel>
<ControlTemplate>
<Setter.Value>
<Setter>
<Style>
<telerik:RadComboBox.ItemContainerStyle>
</telerik:RadComboBox>
<Grid>
"Name" is a string and "Items" is an ObservableCollection

WPF - Panel seems to load all item at once

I want to show 2000 items with image (thumbnail).
If I use normal list view, it will consume about 2GB memory with UI virtualization. Therefore, I tried to handle this problem using Data Virtualization followed [https://github.com/lvaleriu/Virtualization]. The idea is to load only a page of items. When we scroll down until access more than half of a page, a new page is requested. Hence, only several pages are stored in the memory and the memory consumed is about 150MB.
All good until I want to display items in a grid view (like using wrap panel or uniform gird). The problem now is that all pages are requested at the beginning leading to 2GB memory used. I investigate a bit and the problem may lie on itemspanel. It tries to load all data to locate the item and that what's exactly what I am trying to avoid.
Does anyone meet similar problem? Can you help me figure it out?
Thank you in advanced
<ListView ItemsSource="{Binding}" VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.IsDeferredScrollingEnabled="True">
<!-- Problem when I add this part-->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!-- End -->
<ListView.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="MinWidth" Value="200"/>
<Setter Property="MaxWidth" Value="250"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="True" MinWidth="200" MaxWidth="250">
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Data.FullPath}"
TextAlignment="Center" DockPanel.Dock="Bottom" Width="auto"/>
<Image Source="{Binding Data.FullPath, Converter={local:FileToIconConverter}}"
DockPanel.Dock="Top" MaxHeight="200"/>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Data virtualization code can be found in [https://github.com/lvaleriu/Virtualization]. I won't post here since it is a bit complicated.

ListViewItem in WrapPanel occupying space when collapsed

I have a ListView using a WrapPanel as its ItemsPanel, and I use ListViewItem directly as content. But when one ListViewItem.Visibility is Collapsed, you can still see the space it's using.
First off, a sample XAML code similar to what I use :
<Grid>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Hidden" ItemContainerStyle="{DynamicResource ContainerStyle}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}" x:Key="ContainerStyle">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemHeight="200" ItemWidth="200"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Red"/>
</ListViewItem>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Blue"/>
</ListViewItem>
<ListViewItem Margin="10" Visibility="Visible">
<Border Background="Green"/>
</ListViewItem>
</ListView>
</Grid>
For example, when all items are visible (code above) I have this :
But if I change the first item to make it collapsed as follows
<ListViewItem Margin="10" Visibility="Collapsed">
<Border Background="Red"/>
</ListViewItem>
The result is like this :
What I would expect would be the following :
As such I don't understand why it is acting like this, the Collapsedseems to behave just like Hidden. I'm applying it directly to the item and don't see what else to do .
I've tried different solutions I found, most notably this one about binding to Visibility in the style and this one going more or less in the same direction but without success, same results.
The accepted answer actually does not provide a solution, which is instead delivered in its comment section.
If you set ItemWidth, WrapPanel will reserve the ItemWidth for all the items bound to itself, visible or not.
The workaround here is not to set ItemWidth on the WrapPanel but set the Width on the ItemTemplate.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel MinWidth="96" />
</DataTemaplate>
</ItemsControl.ItemTemplate>
I think that the issue you see is related to the fact that even collapsed, a ListViewItem is still an Item, and the WrapPanel will detect 3 items, not 2.
A good working solution seems to be overriding the "Arrange" methods of Panel, in a custom Panel.
I'm working on the base of this great class AlignableWrapPanel (inheriting from Panel, not WrapPanel), and I got it working by replacing this :
var child = children[i];
if (child == null) continue;
with this :
var child = children[i];
if (child == null) continue;
if (child.Visibility == Visibility.Collapsed) continue;
The methods are ArrangeOverride, ArrangeLine, and MeasureOverride. ArrangeLineis a bit different, but the line with if(child == null) continue;is pretty easy to spot ; just add the one with Collapsed after that.
That solution should work with any

WPF ListView/Gridview allow users to select multiple items and group them together

I have a WPF ListView/GridViwe in a MVVM application. GridView is bound to a List in the ViewModel.
The requirement is that the users should be able to select multiple rows of the gridview, right-click on it and see a context menu "Group These Together". Once selected, all these items should be collapsed into one group with a expander or + sign added at the beginning.
Can somebody please help me in getting this working?
I was working on similar problem, but I had to group items by their let's say name. What I've done was create DataTrigger or MultiDataTrigger depending on your data requirements and then when conditions are true i.e. item selected change the container for GroupItem. What I mean is when you create your list view, you have to create it with grouped view, which btw is not grouped as you can declare it without expander and just use the StackPanel. After that you need 3 lines of Code to set the grouping. Here is an example for you:
MAIN.xaml
<ListView
ScrollViewer.CanContentScroll="False"
x:Name="lsvProducts"
ItemsSource="{Binding Products, NotifyOnSourceUpdated=True}"
MouseDown="lsvProducts_MouseDown">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Code" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Width="Auto" Header="Description" DisplayMemberBinding="{Binding Desc}"></GridViewColumn>
<GridViewColumn Width="Auto" Header="Qty" DisplayMemberBinding="{Binding Qty}"></GridViewColumn>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupedView}"/>
</ListView.GroupStyle>
</ListView>
As you can see I have declared an empty container for the grouped style, reson why is because you can't assign it without previous declaration. After this declaration you need this in your generic.xaml
<Style x:Key="GroupedView" TargetType="{x:Type GroupItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chbx, Path=IsChecked}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="False">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="16" Foreground="DimGray" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" item(s)" FontSize="16" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
After you declared your style for the true value you need to declare one for without expander
<!--This goes in the same style as the prevoius sample code -->
<DataTrigger Binding="{Binding ElementName=chbx, Path=IsChecked}" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
Next step is to define the grouping:
//lsvProducts is our ListView
view = (CollectionView) CollectionViewSource.GetDefaultView(lsvProducts.ItemsSource);
//in the creation parameter you should put field that you want to group by :-)
PropertyGroupDescription grouping = new PropertyGroupDescription("FieldToGroupBy");
view.GroupDescriptions.Add(grouping);
Which should apper in your ViewModel.
Good luck!
Let us know if you need any more help, I'll try my best with my English ;-)
EDIT
I forgot to mention when you assign the grouping you need to check the number of groups already in the collection and only apply it when there is non or 0, otherwise you'll apply the grouping multiple times and the expander will be repeeated in the result window as many times as you added the grouping :-)
What in my mind to select multiple row Ctrl+click
It will select multiple row and on right click open a context menu after setting its Isopen to true
You should also bind selected item to list and Use this item to make an expander

Is there something like a WPF listview without the selection?

The application I'm developing has a kind of accordion made of expanders but each exapnder acts independently rather than only allowing a single open item at a time. Each expander is related to an item in a collection in the view model.
My currently solution is to use a listbox and then bind the lists itemsource to the collection and have an item template render the expander and the exapnders contents.
The problem is that the listbox treats each expander as an item (obviously) and allows selection and highlighting. Highlighting is a little ugly and could be disabled, but the selection causes me some issues because it causes the list to scroll to show as much of the expanded expander as possible.
Is there a WPF control that is a little like a stackpanel (perhaps) that would allow me to bind the contained controls using item templating but without the selection and highlighting?
<ListBox Grid.Row="0" Grid.Column="0" Width="Auto" SelectionMode="Single" ItemsSource="{Binding Path=MeasurementSources}">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" IsEnabled="{Binding Available}">
<ListBox Width="Auto" SelectionMode="Single"
ItemsSource="{Binding Path=Measurements}"
SelectedItem="{Binding Path=SelectedMeasurement}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=" "/>
<TextBlock Text="{Binding Created}"/>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you want List-like capabilities without selection capabilities, you should use an ItemsControl - incidentally the base class of Selector which in turn is the base class of ListBox
The whole thing then just becomes
<ItemsControl Width="Auto" ItemsSource="{Binding Measurements}"
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text=" "/>
<TextBlock Text="{Binding Created}"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Obviously, you cannot bind a selected item in this case.
This is a useful piece of information that I had not realised.
http://msdn.microsoft.com/library/windows/apps/hh780657.aspx
Note ItemsControl is the base class for several common collection
controls, including ListView, GridView, FlipView, ListBox, and
ComboBox controls. These examples use the ListView and GridView
controls, but the info applies generally to ItemsControls.
In other words using the ItemsControl really is the way to solve my issue because using a ListBox and then trying to disable the highlight and the selection functionality really is turning it back into it's own base class.
The best solution is for me:
<Style TargetType="{x:Type ListBoxItem}">
<Style.Resources>
<!-- With focus -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent" />
<!-- Without focus -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Transparent" />
<!-- Text foreground -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}"
Color="Black" />
</Style.Resources>
<Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter>
</Style>
and I found it few months ago somwhere on stackoverflow :)

Resources