Hopefully I can explain what I need and what the problem is
I have the followings list box
<ListBox Margin="0,8,0,0" toolkit:TiltEffect.IsTiltEnabled="True" x:Name="ImageList" ItemsSource="{Binding Images}" HorizontalAlignment="Center" BorderThickness="4">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding imageID, Converter={StaticResource ImageConverter}}" Width="125" Height="125" Margin="6" Tap="list_OnTap">
<TextBlock Name="description" Foreground="{Binding TextColor}" Text="{Binding text}" Visibility="{Binding ShowText, Converter={StaticResource BooleanToVisibilityConverter}}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="False" Name="deletectx">
<toolkit:MenuItem Header="delete" Click="delete_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The above code makes a grid of images, I used Grid background to show the image because I needed to show my text on the image (Not sure if there is any other way to do this)
This page load about 30 images with the size 125x125 pixel and about 4kb each. I noticed that it consumes a lot of memory. I read some posts here about clearing the cache of the images, but I don't know how I'm supposed to do that with the above code considering I'm setting a grid background as my image not an image control.
I might be able to access the grid inside the listbox, but whatever I do with it, will be applied to the first image only not the rest. I need to clear the images cache on my navigate away event.
Another question , I also have some performance problem , entering this page takes a little bit of time and I get low frame rate warning in Windows Phone App Analyser, not sure if what I'm doing (loading images through Converter for each listbox item) is right or not !
How can I make this faster ?
To be sure that you can reclaim any memory used by the default image caching behaviour you can use to following: (snipped from another project and edited slightly but should work as is.)
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
if (e.NavigationMode == NavigationMode.Back)
{
// Unload all images so as to reclaim any allocated memory
foreach (var child in VisualTreeHelperEx.GetVisualChildren(this.ImageList).Where(c => c is Image))
{
var image = child as Image;
if (image != null)
{
var bitmapImage = image.Source as BitmapImage;
if (bitmapImage != null)
{
System.Diagnostics.Debug.WriteLine("unloading " + bitmapImage.UriSource);
bitmapImage.UriSource = null;
}
image.Source = null;
}
}
}
}
Which uses this helper:
public static class VisualTreeHelperEx
{
public static IEnumerable<DependencyObject> GetVisualChildren(DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return GetVisualChildrenAndSelfIterator(element).Skip(1);
}
public static IEnumerable<DependencyObject> GetVisualChildrenAndSelfIterator(DependencyObject element)
{
Debug.Assert(element != null, "element should not be null!");
yield return element;
int count = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(element, i);
}
}
}
This may have it's own performance issues so use with care and test the impact on actual performance.
The performance problem on page load is probably due to all the images being loaded at once. You could drip feed them into the list after the page has loaded to avoid this. If the actual images are larger than the background area you should set the DecodePixelHeight and DecodePixelWidth. It may also be worth removing the converter and adding a property with the full path as lots of complex converters can impact performance.
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'm using ListViews to make a kanban list in UWP. As the picture below shows, re-ordering items a few times results in the content of one or some of them being wrong.
Further re-ordering will have the content going back and forth being correct and wrong and everything is back to normal when re-loading the page which means there's not data change but just the image control displaying the wrong image. ( It can happen with any other control too )
For reference, The images are local files which I'm loading in the Image control's Loaded event, and the ListView simply has CanReorderItems and AllowDrop set to true.
Here's how the XAML looks
<ListView x:Name="LView" MinWidth="240" Grid.Row="1" ItemsSource="{x:Bind List.Tasks}" ReorderMode="Enabled" CanReorderItems="True" AllowDrop="True" CanDragItems="True" SelectionMode="None" IsItemClickEnabled="True" ScrollViewer.VerticalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollMode="Enabled" ScrollViewer.IsVerticalRailEnabled="True" ItemClick="LView_ItemClick">
<ListView.ItemContainerStyle>
...
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="mongo:Task">
<Grid Padding="12 0" >
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Border CornerRadius="4 4 0 0" Margin="-12 0" >
<Image x:Name="Cover" MaxHeight="160" Stretch="UniformToFill" Tag="{x:Bind Attachments}" Loaded="Image_Loaded" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Border>
...
And here's the Loaded event
private async void Image_Loaded(object sender, RoutedEventArgs e)
{
var img = sender as Image;
if (img.Source is object) return;
var attachments = img.Tag as ObservableCollection<TaskAttachment>;
if (attachments is null) return;
var cover = attachments.Where(_a => _a.is_cover).FirstOrDefault();
if (cover is object && cover.type == "image")
{
var path = BrandboxSettings.Instance.server_path + "projects\\" + cover.path;
Output.WriteLine(path);
var file = await StorageFile.GetFileFromPathAsync(path);
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
// Set the image source to the selected bitmap
BitmapImage bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(fileStream);
img.Source = bitmapImage;
}
}
}
Edit: It's worth noting that even if one of the cards does not initially have an image, reordering will cause it to have one.
Any help would be appreciated
Okay so I tried changing the ItemsPanel to a StackPanel and it seems to be working now.
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizationStackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
Edit:
It also seems to work by setting the Panel to VirtualizationStackPanel
I am working on a Windows Phone 8.1 app in XAML/C#.
I have a listview, whose item source is set to a CollectionViewSource called MusicSource. On the backend in C#, I have an ObservableCollection called source and the following code populates it by getting getting all the music files on the phone, groups it by artist and then puts them in the CollectionViewSource, which shows them in the listview:
var folders = await folder.GetFoldersAsync();
if (folders != null)
foreach (var fol in folders)
await getMusic(fol);
var files = await folder.GetFilesAsync();
foreach (var file in files)
{
MusicProperties musicProperties = await file.Properties.GetMusicPropertiesAsync();
this.source.Add(new Music((musicProperties.Artist.Length > 0) ? musicProperties.Artist : "Custom", (musicProperties.Title.Length > 0) ? musicProperties.Title : file.Name, (musicProperties.Album.Length > 0) ? musicProperties.Album : "Custom Album", file.Path));
}
itemSource = AlphaKeyGroup<Music>.CreateGroups(source, CultureInfo.CurrentUICulture, s => s.Artist, true);
this.MusicSource.Source = itemSource;
The following is the XAML side of it:
<Page.Resources>
<DataTemplate x:Key="GroupTemplate">
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1">
<TextBlock x:Name="SongTitle" Text="{Binding Title}"
Style="{ThemeResource ListViewItemTextBlockStyle}"/>
<TextBlock x:Name="ArtistName" Text="{Binding Album}"
Style="{ThemeResource ListViewItemContentTextBlockStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
<CollectionViewSource x:Name="MusicSource" IsSourceGrouped="true" />
<DataTemplate x:Key="headerTemplate">
<StackPanel HorizontalAlignment="Stretch" Width="{Binding ActualWidth, ElementName=contentList}">
<TextBlock Text="{Binding Key}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid>
<SemanticZoom>
<SemanticZoom.ZoomedInView>
<ListView
x:Name="contentList"
SelectionMode="Multiple"
ItemsSource="{Binding Source={StaticResource MusicSource}}"
ItemTemplate="{StaticResource GroupTemplate}">
<ListView.GroupStyle>
<GroupStyle HidesIfEmpty="True" HeaderTemplate="{StaticResource headerTemplate}"/>
</ListView.GroupStyle>
</ListView>
</SemanticZoom.ZoomedInView>
</SemanticZoom>
<Border
x:Name="SearchBorder"
Background="White">
<TextBox
x:Name="Search" TextChanged="TextBox_TextChanged" />
</Border>
</Grid>
So I get something like the following in the listview:
Michael Jackson
Bad
Dangerous
Thriller
Monster
Eminem
Not Afraid
The Monster
When the user types in the search textbox, the listview should be filtered and only show the items that match the text in the search textbox. So for example, if I type "Monster" in the searchbox, the listview is immediately filtered and only shows "Monster" within the "Michael Jackson" group header and "The Monster" within the "Eminem" group header.
How would I achieve this?
I've got similar task for Windows 8 Store Application - live filtering of the grouped list view when user type sample text. I've made a view model that holds FilterText and Groups. Group has two observable collections - AllItems (complete list of items) and Items (visible on screen). When FilterText is changed I'm going through each item inside each group and determine whether to keep it in Items or not. Here is some code:
...
var rule = x => GetTextToFilter(x).IndexOf(filterText,
StringComparison.CurrentCultureIgnoreCase) >= 0;
foreach (var group in Groups)
{
group.UpdateVisibleItems(rule);
}
...
void UpdateVisibleItems(Func<ItemViewModel, bool> rule)
{
for (int i = 0, j = 0; i < AllItems.Count; i++)
{
var item = AllItems[i];
if (rule(item))
{
if (j == _Items.Count || (j < _Items.Count && _Items[j] != item))
{
_Items.Insert(j, item);
}
j++;
}
else
{
if (j < _Items.Count && _Items[j] == item)
{
_Items.RemoveAt(j);
}
}
}
}
This way selection stays intact if item is still visible after filtering. Animations looks correct because system see it as series of insert/remove on observable collection (complete list refresh will make whole list removed and restored which will be animated wrong).
It works great (provided that AllItems is not millions of rows), but I'm hitting strange exception in specific case - when ListView.GroupStyle.HidesIfEmpty=True and some (or all?) groups are cleared in the process of update - process crashes within Windows.UI.Xaml.dll. No exception is trapped in C# code (UnhandledException and TaskScheduler.UnobservedTaskException are silent). Nothing usable in Event Log. Debugger fails to display details or even attach to the failing process. If I add await Task.Delay() it will be way more stable, but still may fail from time to time. If I set ListView.GroupStyle.HidesIfEmpty=False - all is stable working!
Hmm, it doesn't seem hard at all , all you have to do is to first make your songs list then search among it's items and make unwanted item COLLAPSED!
for ease of work use listview control and search in its items .
Note that first u have to make a cache of all listview controls in mother control for future searches , to avoid getting all musics again and also disabling search mode.
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
In my WP8 app, i have a page with lisbox where i am binding list of images to in the ItemTemplate with other data. As soon as I leave the page, i feel these images are not freeing up from the memory.
Below are code details:
XAML
<ListBox x:Name="userList" ItemTemplate="{StaticResource DataTemplate1}" Tap="userList_Tap" Loaded="userList_Loaded">
<StackPanel Orientation="Horizontal" Width="220" Height="220" HorizontalAlignment="Center" VerticalAlignment="Center" >
<Image x:Name="episodeImage" HorizontalAlignment="Right" Height="120" Margin="0" VerticalAlignment="Top" Width="120" Source="{Binding DefaultImagePath}" />
<TextBlock x:Name="episodeName" HorizontalAlignment="Left" Margin="4,0,0,36" TextWrapping="Wrap" Width="Auto" Foreground="White" FontFamily="Segoe WP" Text="{Binding ImageName}" VerticalAlignment="Bottom"/>
</StackPanel>
</ListBox>
C# data behind:
public class ImageHolder{
public BitmapImage DefaultImagePath { get; set; }
public string ImageName { get; set; }
}
// list binding
List<ImageHolder> images=Utils.GetLargeImages();
userList.ItemSource=images;
public static List<ImageHolder> GetLargeImages(){
List<ImageHolder> images= new List<ImageHolder>();
for (int i = 0; i < 10; i++)
{
ImageHolder hold=new ImageHolder();
hold.ImageName=i+"";
hold.DefaultImagePath = new BitmapImage
{
DecodePixelWidth = 120,
DecodePixelHeight = 120,
UriSource = new Uri("Image_"+i+".png", UriKind.RelativeOrAbsolute) // this image is in 400x400 size
};
images.Add(hold);
}
return images;
}
I am using DecodePixelWidth and DecodePixelHeight during image fetching in (GetLargeImages() method).
onnavigatedfrom method i am setting null to the Listbox. But doesn't helping it, after couple of times going in and out of pages my app is crashing with OutofMemoryException.
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
userList.ItemsSource = null;
}
Try something that normally should not be done. By calling the Garbage collector.
GC.Collect();
You cann also try when you navigate away to another page to remove navigation backstack which should remove all knowledge of your page and thereby force a reinstantiation of the list and page when going back.