I am using a GridView with a template selector and passing the item source in the code behind. The problem with this is that the VariableSizedWrapGrid is really slow, the collection passed being about 80 items big (collection is composed of a few strings). Removing the variableSizedWrapGrid solves the issue but leaves me with large gaps between the templates having smaller width.
here is the GridView:
<SemanticZoom x:Name="Zoom" Grid.Row="1" IsZoomedInViewActive="False" ViewChangeStarted="Zoom_ViewChangeStarted_1" IsZoomOutButtonEnabled="False" Margin="0,0,0,29" Grid.RowSpan="2">
<SemanticZoom.ZoomedInView>
<!-- Horizontal scrolling grid used in most view states -->
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemsGridView"
AutomationProperties.Name="Items"
TabIndex="1"
ItemTemplateSelector="{StaticResource DayTemplateSelector}"
SelectionMode="None"
IsSwipeEnabled="True"
ItemContainerStyle="{StaticResource GridViewItemStyle1}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
IsItemClickEnabled="True"
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
ItemClick="ItemView_ItemClick">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="380" ItemHeight="500"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</SemanticZoom.ZoomedInView>
And the Template Selector:
class DayTemplateSelecter : DataTemplateSelector
{
public DataTemplate DayOffTemplate { get; set; }
public DataTemplate DutyTemplate { get; set; }
public DataTemplate RestTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var templateItem = item as DutyItem;
var element = container as FrameworkElement;
if (templateItem.Trip.Count > 0 || templateItem.OtherTrip.Count > 0)
{
container.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 2);
return DutyTemplate;
}
else if (templateItem.Codes.Count > 0)
{
container.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
return DayOffTemplate;
}
else
{
container.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
return RestTemplate;
}
}
}
I didn't think this would have such a large performance difference..just using variableSized would give the page 3 seconds of delay and this is even larger on some low end tablets.
Am I doing it wrong, is there a better way to do this?
VariableSizedWrapGrid unlike WrapGrid doesn't virtualize its items. I would suggest that if you have to show 80 items use WrapGrid or if you have to use VariableSizedWrapGrid reduce amount of items to a more manageable level.
Related
I have a ListView that is intended to show every product within a database, and it works for the most part, but when I scroll down by dragging the scroll bar, the bottom items end up being incorrect.
XAML Definition:
<ListView x:Name="lst_Products" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="16,124,16,16" Width="300" ContainerContentChanging="lst_Products_ContainerContentChanging" Loaded="lst_Products_Loaded" BorderBrush="Black" BorderThickness="2" CornerRadius="16">
<ListView.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Value}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The data template is present so I can easily grab a product ID number with SelectedValue. According to some trusted community member (or whatever they call the prominent posters) on the MSDN forums said that's the only way to properly show a ListView when the ItemsSource is an ObservableCollection<KeyValuePair<int,RelativePanel>> while having a selectable value member.
The relevant C# code:
private async void lst_Products_Loaded(object sender, RoutedEventArgs e)
{
var products = await ProductManager.GetProducts();
ObservableCollection<KeyValuePair<int, RelativePanel>> productList = new(products);
lst_Products.ItemsSource = productList;
lst_Products.SelectedValuePath = "Key";
}
private void lst_Products_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.ItemIndex % 2 == 1)
{
args.ItemContainer.Background = new SolidColorBrush(Color.FromArgb(128, 128, 128, 128));
}
else
{
args.ItemContainer.Background = UIManager.GetDefaultBackground();
}
}
public static async Task<List<KeyValuePair<int, RelativePanel>>> GetProducts()
{
var productPanels = new List<KeyValuePair<int, RelativePanel>>();
var productIDs = await SqlHandler.ReturnListQuery<int>($"SELECT id FROM {productTable}");
var productNames = await SqlHandler.ReturnListQuery<string>($"SELECT name FROM {productTable}");
var panels = new List<RelativePanel>();
foreach(var name in productNames)
{
RelativePanel panel = new();
TextBlock productName = new()
{
Text = name
};
panel.Children.Add(productName);
panels.Add(panel);
}
for(int i = 0; i < productIDs.Count; i++)
{
productPanels.Add(new KeyValuePair<int, string>(productIDs[i], panels[i]));
}
return productPanels;
}
The call to SQL Handler just runs an SQL query and returns a list of the results. I can post the code if you need, but I can assure you there's no sorting going on.
A screenshot of what the list looks like. The bottom item should be "Coffee" - Button Test Product 2 is the second item in the list.
A screenshot of the SQL datatable with the "Coffee" product at the bottom where it should be.
In this case it's just the bottom item that's incorrect, however other times it has jumbled 5 or 6 entries near the bottom. This only seems to occur with the DataTemplate/ContentPresenter, but without that, the RelativePanel does not display correctly in the list. Eventually the list will show more information about the product and as far as I can tell, there's no good way to do that without converting the SQL data into a RelativePanel on the c# side.
I'm open to suggestions on solving either the jumbling problem with the template, or adjusting the xaml so that I don't need the template to display bulk sql data without needing the template but I'm at a loss.
c# - UWP ListView displays incorrect items upon rapid scrolling when it has a DataTemplate
The problem should be caused by listview virtualization, There are two ways to sloved this prolbem, one is disalbe listview virtualization by setting ItemsPanel as StackPanel like the following
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
And the other way is implement INotifyCollectionChanged interface for your model class. for more please refer to Data binding in depth
It's not good practice that useRelativePanel collection as datasoure, the better way is make RelativePanel in your DataTemplate and bind with mode class property.
For example
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Index}" />
<TextBlock Text="{Binding IsItem}" />
<Image Source="{Binding ImageSource}" Visibility="Collapsed" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
I want to layout my items in a Windows Phone 8.1 app, not silverlight, in the following order:
I did some research and tried different panels, but I can't find the right ones :[
I could use a grid and achive that design, BUT I want to add items over a binding and then I would have to change the grid somehow :/
xaml Layout
<Page.DataContext>
<uc:Test/>
</Page.DataContext>
<ScrollViewer>
<ItemsControl ItemsSource="{Binding t}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Aqua"
BorderThickness="3"
Width="100" Height="100">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
test.cs
public class Test
{
public ObservableCollection<string> t { get; set; }
public Test()
{
t = new ObservableCollection<string>();
t.Add("a");
t.Add("b");
t.Add("c");
t.Add("d");
t.Add("e");
}
}
Edit:
ALSO, I did write a wrong information in the comment below, sorry.
Every Item has the same width, so count and width, will/would give me the position in column and row.
Implementation of PrepareContainerForItemOverride so far:
public class ExtendedItemsControl : ItemsControl
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var grid = element as ContentPresenter;
var count = 0; // <- Count of Items in the Grid
var width = 0; // <- width of the current Element
//if (count * width / grid.ActualWidth > 1)
// grid.RowDefinitions.Add(new RowDefinition());
Grid.SetRow(grid, 0);
}
}
You can use a Grid along with an ItemsControl to achieve the ItemsSource binding:
First, set the Grid as the ItemsControl's ItemsPanel
Second, subclass the ItemsControl to set the appropriate Grid.Row and Grid.Column properties on its children
For the first part (it looks from the picture like you have 4 columns and 3 rows):
<local:ExtendedItemsControl ItemsSource="{Binding MyItems}">
<local:ExtendedItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</local:ExtendedItemsControl.ItemsPanel>
</local:ExtendedItemsControl>
For the second part, I suggest overriding OnItemsChanged, and setting the Grid attached properties on each item container as needed. You could do this by using the implicit sequence of the items:
public class ExtendedItemsControl : ItemsControl
{
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
var item2 = this.ItemContainerGenerator.ContainerFromItem(e.NewItems[1]);
Grid.SetColumn(item2, 1);
var item3 = this.ItemContainerGenerator.ContainerFromItem(e.NewItems[2]);
Grid.SetColumn(item3, 2);
Grid.SetColumnSpan(item3, 2);
var item4 = this.ItemContainerGenerator.ContainerFromItem(e.NewItems[3]);
Grid.SetRow(item4, 1);
// etc ...
}
}
The above assumes that your source collection doesn't not change once bound -- if it does change, you might consider overriding PrepareContainerForItemOverride instead, and setting its Grid Row/Column properties with reference to a property on the item model ("ItemIndex" or whatever):
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var contentPresenter = (ContentPresenter)element;
var itemModel = (MyItemModel)item;
switch (itemModel.ItemIndex)
{
case 1:
Grid.SetColumn(contentPresenter, 1);
break;
case 2:
Grid.SetColumn(contentPresenter, 2);
Grid.SetColumnSpan(contentPresenter, 2);
break;
// etc
}
}
There isn't a standard control that will give you the layout you want for arbitrary numbers of different sized items without some custom placement code, but you can customize controls depending on what exactly you need.
Mark Rideout created a customized GridView sample for Windows Store 8.0 at How To: Create a Variable Sized Grouped GridView (like the store) and the techniques you'll use for a Windows Phone Runtime app will be essentially the same. In his control he overrode the PrepareContainerForItemOverride function to look at the individual data items to see if they should be small, medium, or large sized, and then set their columns and spans appropriately in a VariableSizedWrapGrid.
If you want the exact positioning you show (rather than lining things up) and want to limit to 7 then you could set the ItemsPanel to a Grid instead of the VariableSizedWrapGrid and set the items into specific rows and columns in the same way.
I am working on windows phone 8 app.
I have List box with over 200 items to display.
<DataTemplate x:Key="DataTemplate1">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="White" Height="400" Width="400" CornerRadius="30,30,30,30">
</Border>
<Grid Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="5,20,5,5"
Foreground="#000000"
Text="{Binding Title}"/>
</Grid>
</Grid>
</DataTemplate>
But it crashes, i have debugged it till 100 items it works but after that it crashes.
In the PhoneApplicationPage_Loaded method i have
private void PhoneApplicationPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
myList.Add(new MyObject("A","A value"));
myList.Add(new MyObject("B", "B value"));
myList.Add(new MyObject("C", "C value"));
and so on... 200 items
ListBoxItems.ItemsSource = myList;
}
how can i fix this ?
Update :
<ItemsPanelTemplate x:Key="ItemsPanelTemplate">
<local:CollectionFlowPanel ItemHeight="400"
ItemWidth="400"
FocusedItemOffset="120"
UnfocusedItemOffset="20"
ItemVisibility="5">
<VirtualizingStackPanel />
</local:CollectionFlowPanel>
</ItemsPanelTemplate>
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot" Background="#000000">
<local:CollectionFlow x:Name="ListBoxItems"
ItemTemplate="{StaticResource DataTemplate}"
ItemsPanel="{StaticResource ItemsPanelTemplate}"/>
</Grid>
Ensure you have VirtualizingStackPanel inside the ItemsPanelTemplate of your list box, see this answer for more info.
Here's the XAML you likely need for your ListBox:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
You need to read following blog from msdn on visualization of the data in list and grid.
Using virtualization with a list or grid
Without seeing your whole xaml code I cannot suggest the exact answer but my guess is that you in xaml ListBox is placed inside a canvas/StackPanel or scrollviewer control.
When the size of the ItemsControl's viewport isn't restricted, the control doesn't perform virtualization. Instead, it creates an item container for each item in its collection. Some common containers that don't restrict the viewport size are Canvas, StackPanel, and ScrollViewer. You can enable virtualization in this situation by setting the size of ItemsControl directly, instead of letting it be sized by its parent container.
Here, we set the Height and Width on the GridView. This restricts the size of the viewport, and items outside of the viewport are virtualized.
Below are 2 scenarios one will throw out of memory exception and other will work fine(use your same code behind and test)
1. ListBox in Canvas
<Canvas .....
<ListBox Name="ListBoxItems".....
</ListBox>
</Canvas>
Above code will throw out of memory exception as items control's viewport is not defined (if you still want to use Canvas than define width/height if ListBox in that case the port of Items control is defined and virtulazation will apply)
2. ListBox in Grid
<Grid .....
<ListBox Name="ListBoxItems".....
</ListBox>
</Grid>
The above code will not throw out of memory exception as virtuallization is applied on the listbox.
Hope this will help
How big is your object ? If your object is too big you might not be able to load them all at once.
Did you try using the for loop?
public List<Fellow> fellowList { get; set; }
private void PhoneApplicationPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
fellowList = new List<Fellow>();
for (int i = 0; i < 2; i++)
{
Fellow fellow = new Fellow();
fellow.x = "B" + i;
fellow.value = "B Value" + i;
fellowList.Add(fellow);
}
this.DataContext = this;
ListBoxItems.ItemsSource = fellowList;
}
public class Fellow
{
public string x { get; set; }
public string value { get; set; }
}
Hope it helps..change the view model according to your wish
My GridView items having the size of it's first item size. How do i can change this behaviour ?
How to display GridView items with variable Width as per the content ?
I want to show the first one but i am getting second one. Any suggestion to do that?
Check Windows 8 GridView and Variable-Sized Items and Different Sized Tile Items in WinRT GridView and also check Variable Sized Grid Template Hope this help
You can create such view of GridView by setting ItemsPanel to WrapPanel, you can get WrapPanel on Jerry Nixon's blog. Here's the code.
XAML
<GridView x:Name="gv">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<local:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Height="140" Width="{Binding MyWidth}">
<Grid.Background>
<SolidColorBrush Color="{Binding MyColor}" />
</Grid.Background>
<TextBlock FontSize="20" VerticalAlignment="Bottom" Margin="10,0,0,10">
<Run Text="{Binding MyWidth}" />
</TextBlock>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
C#
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var list = new List<ViewModel>()
{
new ViewModel(110, Colors.LawnGreen),
new ViewModel(50, Colors.DarkBlue),
new ViewModel(130, Colors.Firebrick),
new ViewModel(60, Colors.RosyBrown),
new ViewModel(100, Colors.IndianRed),
new ViewModel(210, Colors.BurlyWood),
new ViewModel(150, Colors.Turquoise)
};
gv.ItemsSource = list;
}
public class ViewModel
{
public double MyWidth { get; set; }
public Color MyColor { get; set; }
public ViewModel(double _MyWidth, Color _MyColor)
{
MyWidth = _MyWidth;
MyColor = _MyColor;
}
}
Here is my solution.
//variable sized grid view
public class VariableSizedGridView : GridView
{
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
{
try
{
dynamic gridItem = item;
var typeItem = item as CommonType;
if (typeItem != null)
{
var heightPecentage = (300.0 / typeItem.WbmImage.PixelHeight);
var itemWidth = typeItem.WbmImage.PixelWidth * heightPecentage;
var columnSpan = Convert.ToInt32(itemWidth / 10.0);
if (gridItem != null)
{
element.SetValue(VariableSizedWrapGrid.ItemWidthProperty, itemWidth);
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, columnSpan);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
}
}
}
catch
{
element.SetValue(VariableSizedWrapGrid.ItemWidthProperty, 100);
element.SetValue(VariableSizedWrapGrid.ColumnSpanProperty, 1);
element.SetValue(VariableSizedWrapGrid.RowSpanProperty, 1);
}
finally
{
base.PrepareContainerForItemOverride(element, item);
}
}
//Collection View source
<CollectionViewSource x:Name="collectionViewSource"
Source="{Binding ImageList,Mode=TwoWay}"
IsSourceGrouped="False"
ItemsPath="Items" />
//variable sized Grid view xaml
<controls:VariableSizedGridView x:Name="ImageGridView"
AutomationProperties.AutomationId="ImageGridView"
ItemsSource="{Binding Source={StaticResource collectionViewSource}}"
IsItemClickEnabled="True"
TabIndex="1" >
<controls:VariableSizedGridView.ItemTemplate>
<DataTemplate>
<Grid Height="300" >
<Image Stretch="Uniform" Source="{Binding WbmImage}" />
</Grid>
</DataTemplate>
</controls:VariableSizedGridView.ItemTemplate>
<controls:VariableSizedGridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid ItemWidth="10" ItemHeight="300" Orientation="Vertical"/>
</ItemsPanelTemplate>
</controls:VariableSizedGridView.ItemsPanel>
</controls:VariableSizedGridView>
I've been checking out the UI virtualization feature for WPF's TreeView control, which as I understand, is available since .NET 3.5 SP1.
I made a simple project to make sure that UI virtualization is performed correctly, and found out that it doesn't work at all - all of the items are retrieved rather than just the ones currently displayed on the screen.
My XAML looks like this
<TreeView x:Name="myTree" Height="150" ItemsSource="{Binding Items}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
ScrollViewer.IsDeferredScrollingEnabled="True" />
And my code behind
public IEnumerable Items { get; set; }
public MainWindow()
{
Items = GenerateList();
this.DataContext = this;
InitializeComponent();
}
private IEnumerable GenerateList()
{
MyList list = new MyList();
for (int i = 0; i < 1000; i++)
{
list.Add("Item " + i);
}
return list;
}
Note that MyList is my own implementation of IList that holds an ArrayList and does nothing more than forward calls to the held ArrayList and write to the console which method/property was called. For example:
public object this[int index]
{
get
{
Debug.WriteLine(string.Format("get[{0}]", index));
return _list[index];
}
set
{
Debug.WriteLine(string.Format("set[{0}]", index));
_list[index] = value;
}
}
If I replace my TreeView with a ListBox, UI virtualization works as expected - i.e. only ~20 items are requested and not the whole 1000.
Am I doing something wrong here?
EDIT
I've also tried replacing the default ItemsPanel to VirtualizingStackPanel, as suggested , but I'm getting the same results.
Default ItemsPanelTemplate for TreeView is StackPanel and not VirtualizingStackPanel that's why you can't see virtualization in it. Whereas for ListBox default ItemsPanelTemplate is VirtualizingStackPanel that's why setting VirtualizingStackPanel.IsVirtualizing="True" works for ListBox.
To enable virtualization on your TreeView apart from setting property VirtualizingStackPanel.IsVirtualizing="True", you need to override the its default itemsPanelTemplate like this -
<TreeView x:Name="myTree" Height="150" ItemsSource="{Binding Items}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
VirtualizingStackPanel.CleanUpVirtualizedItem="myTree_CleanUpVirtualizedItem"
ScrollViewer.IsDeferredScrollingEnabled="True">
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>