I'm writing an UWP app in C#. I have a page with a ListView taking all space available.
The list of items is huge so I use IncrementalLoadingCollection to load items gradually in the ListView while user is scrolling.
The problem is when I start the app all items are loaded right away in the ListView. GetPagedItemsAsync is called over and over and the app crashes throwing Layout cycle detected.
The only way I get the scroll loading to work is by fixing the ListView or height to a certain value. With fixed height on the ListView GetPagedItemsAsync is only called once at initialization and the scroll loading works as expected.
The thing is I want the ListView to fill page content so I don't want to set height to a specific value.
Page content
<Grid
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListView
x:Name="channelList"
ItemsSource="{x:Bind Channels}"
DataFetchSize="100"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Page class
public sealed partial class ChannelsPage : Page
{
private IncrementalLoadingCollection<ChannelSource, ChannelModel> Channels { get; set; }
public ChannelsPage()
{
this.InitializeComponent();
Channels = new IncrementalLoadingCollection<ChannelSource, ChannelModel>();
}
private class ChannelSource : IIncrementalSource<ChannelModel>
{
private int Current { get; set; } = 0;
private int Limit { get; set; } = 100;
public async Task<IEnumerable<ChannelModel>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken))
{
List<ChannelModel> channels = ChannelModel.List(false, Limit, Current);
Current += Limit;
return channels;
}
}
}
[Solved]
The ListView was expanding out of bound due to a parent ScrollViewer in another file. Because of this continuous expansion out of its parent the items was loading non-stop.
The key to unlocking this mystery is the IncrementalLoadingThreshold. In simple terms it says the system how many "pages" of content to preload. In case you use 5, the system will make sure to have 5 times the height of the control ready to view. Try decreasing the value to 1 and you should see the method will be called once or twice. Even lower values like 0.1 are also possible.
Secondly, ensure you do not put the ListView into any layout control which allows it to expand beyond any bounds. For example, putting the list in a Grid.Row which has RowDefinition of Height="*" will make the list as long as it needs - which means it will keep loading items until there are no more available.
Finally, you need to make sure to indicate when you are out of items. This is easy if you return an empty IEnumerable from the method.
Related
I am developing a program within UWP that will have the ability to chart data on-screen, and must support the potential that a user may want to chart a large number of series on it.
The Chart that I am using is Telerik's RadCartesianChart and I am using a RadLegendControl for showing the legend for the chart. This is laid out on a Grid that has two Columns and one Row. In the first Column (0) is the RadLegendControl. In the second Column (1) is the RadCartesianChart.
When a large number of Series are drawn, this can result in the Legend going down below the bottom of the app, cutting off the remaining items in the legend. This is basically an "excessive" example of the usage of this chart and I'm wanting to make sure it can function effectively when put under this kind of use.
Is there a way to add a scrollbar to the Legend Control so that a user can scroll through the legend? Or should I be looking at a different method for showing the legend?
This is for a program made within UWP that is currently targeting a minimum version of Windows 10 1803 and aiming for 1809, using Visual Studio 2019.
I made a post over on Telerik's forum asking this question and it was suggested that there is possibly an external component that is letting the legend extend to its full height off-screen and they provided a possible solution in trying to set an explicit maximum height to see the scrollbar appear when it reaches that upper bound. As such, in the XAML I set MaxHeight="300", which is much smaller than the average Chart's legend would require, such that I could easily see if the Scrollbar appeared. When I tried this, no Scrollbar appeared.
Originally I was having the RadLegendControl being drawn utilising a StackPanel to reorder the Legend to display from top-down instead of left-to-right so that it could fit alongside the Chart. I suspected that the StackPanel's internal ScrollViewer may have been conflicting with the RadLegendControl's internal ScrollViewer. I removed the StackPanel layout to ensure that it could not conflict to see if a ScrollViewer would then appear. It did not (I tested a horizontal one as well, to no success).
I have tried other solutions such as binding the MaxHeight property of the RadLegendControl to the Height or ActualHeight of the Grid row that it is on, explicitly setting VerticalScrollMode to Enabled and VerticalScrollVisibility to Visible.
This is the RadLegendControl code within XAML, still with the MaxHeight set explicitly to 300:
<telerikPrimitives:RadLegendControl
x:Name="LegendForChart"
LegendProvider="{Binding ElementName=MainChart}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
MaxHeight="300"
>
<telerikPrimitives:RadLegendControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</telerikPrimitives:RadLegendControl.ItemsPanel>
</telerikPrimitives:RadLegendControl>
Where telerikPrimitives is defined with the following:
xmlns:telerikPrimitives="using:Telerik.UI.Xaml.Controls.Primitives"
I have tried adding/modifying the following lines:
MaxHeight="{Binding ElementName=ChartGrid, Path=Height, Mode=Oneway}"
MaxHeight="{Binding ElementName=ChartGrid, Path=ActualHeight, Mode=Oneway}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
Currently with my file that can display ~332 Series on my Chart, the Legend does not have a Scrollbar showing, with the items disappearing off-screen. (Unfortunately I don't have enough rep to show an image).
I would like to find a solution where, if there are sufficient Series showing, a vertical Scrollbar would appear and allow the user to scroll down through the Legend.
I realise this may appear as excessive, but I would like to ensure that my program behaves appropriately if a user would, for any reason, decide to display a large number of Series on the chart.
Since you just provide the RadLegendControl XAML code, I did not see your whole XAML code sample. I'm not sure what the issue is on your side.
So, I made a simple code sample according to the Telerik's official document.
I just use a ScrollViewer control to wrap the RadLegendControl, then it will be scrollable.
Please see my code sample:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="8*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<telerikChart:RadCartesianChart x:Name="chart" Grid.Column="1">
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:CategoricalAxis />
</telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis />
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.SeriesProvider>
<telerikChart:ChartSeriesProvider x:Name="provider">
<telerikChart:ChartSeriesProvider.SeriesDescriptors>
<telerikChart:CategoricalSeriesDescriptor ItemsSourcePath="GetData" ValuePath="Value" CategoryPath="Category" LegendTitlePath="LegendTitle">
<telerikChart:CategoricalSeriesDescriptor.Style>
<Style TargetType="telerikChart:BarSeries">
<Setter Property="CombineMode" Value="Cluster" />
</Style>
</telerikChart:CategoricalSeriesDescriptor.Style>
</telerikChart:CategoricalSeriesDescriptor>
</telerikChart:ChartSeriesProvider.SeriesDescriptors>
</telerikChart:ChartSeriesProvider>
</telerikChart:RadCartesianChart.SeriesProvider>
</telerikChart:RadCartesianChart>
<ScrollViewer>
<telerikPrimitives:RadLegendControl x:Name="LegendForChart" LegendProvider="{Binding ElementName=chart}">
<telerikPrimitives:RadLegendControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</telerikPrimitives:RadLegendControl.ItemsPanel>
</telerikPrimitives:RadLegendControl>
</ScrollViewer>
</Grid>
private Random r = new Random();
public List<ViewModel> GenerateCollection()
{
List<ViewModel> collection = new List<ViewModel>();
for (int i = 0; i < 500; i++)
{
ViewModel vm = new ViewModel();
vm.GetData = GenerateData();
vm.LegendTitle = "ViewModel " + i;
collection.Add(vm);
}
return collection;
}
public List<Data> GenerateData()
{
List<Data> data = new List<Data>();
data.Add(new Data { Category = "Apple", Value = r.Next(1, 20) });
data.Add(new Data { Category = "Orange", Value = r.Next(10, 30) });
data.Add(new Data { Category = "Lemon", Value = r.Next(20, 40) });
return data;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.provider.Source = GenerateCollection();
}
public class ViewModel
{
public List<Data> GetData { get; set; }
public string LegendTitle { get; set; }
}
public class Data
{
public double Value { get; set; }
public string Category { get; set; }
}
I have a listview in my UWP (Windows 10) application.
Ideally it will load 100 items when application starts.
When list is scrolled to bottom i.e. to the last item in listview, API call will go & will load another 100 items & so on..
Here is my code :
<ListView x:Name="lstSuggestions" Height="200" Width="250" Foreground="#333eb4" HorizontalAlignment="Left" Margin="60,10" SelectionChanged="lstSuggestions_SelectionChanged"></ListView>
following call binds the listview (first 100 items on app start) :
public async void GetData(string Id, string limit)
{
string mainUrlForSuggestions = ApiUrl + "&id=" + d;
string finalSelCharas = "";
using (var httpClient = new HttpClient())
{
var dataUri = await httpClient.GetStringAsync(mainUrlForSuggestions);
JsonObject jsonObject = JsonObject.Parse(dataUri.ToString());
JsonArray jsonArray = jsonObject["payload"].GetArray();
foreach (JsonValue groupValue in jsonArray)
{
JsonObject groupObject = groupValue.GetObject();
lstSuggestionsAdd.Add(new SuggestedWords { Name = groupObject.GetNamedString("sug_name"), ID = groupObject.GetNamedString("id") });
}
lstSuggestions.ItemsSource = lstSuggestionsAdd;
}
}
on app start limit is 100, once list reaches to an end, it must set limit to 200 or next 100 items and make an API call again.
I tried to achieve this with pointerEntered event. But, couldn't achieve the said functionality as it only matches the height assigned to listview with pointer height, so that wont work as scrollviewer height can vary. I even tried to get access to scrollviewer, but couldn't!
I have also referred following URL's : How do I allow a UWP ListView to scroll past the last item? && Detect when WPF listview scrollbar is at the bottom? && https://social.msdn.microsoft.com/Forums/windows/en-US/63b4b530-61d8-477f-af96-87e33260c919/uwa-how-to-detect-the-end-and-the-start-of-listview-and-load-more-data-items?forum=wpdevelop
But none of them actually worked in my case.
I tried to find an event to achieve this functionality, but didn't find any.
Can anyone give an idea about how to detect if listview scrolling reached to an end (last item in the listview)???
Note that i am working on windows 10 UWP application & not win 8
It's a bit different; it uses the ListView's incremental loading functionality to create a infinite scrolling list.
This means you won't have as much control of loading the data as you assumed in your question, but still I think it will suit your needs:
It uses MVVM bindings so no typical UI events are used. If you don't know about MVVM, try to duckduckgo it a bit.
First some XAML, the default main page:
<Page
x:Class="App6.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:ViewModel, IsDesignTimeCreatable=True}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding Items}"
DataFetchSize="1"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Text}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Note the
ItemsSource="{Binding Items}"
DataFetchSize="1"
IncrementalLoadingTrigger="Edge"
IncrementalLoadingThreshold="5"
ItemSource will bind to the items collection, its used in the item template
DataFetchSize, the amount to fetch when the end is reached: in PAGES. (confused me for a moment)
IncrementalLoadingTrigger, see msdn
IncrementalLoadingThreshold, see msdn
Then..., the code:
First a custom observable collection: Here is also your load routine:
public class IncrementalLoadingCollection : ObservableCollection<Item>, ISupportIncrementalLoading
{
uint x = 0; //just for the example
public bool HasMoreItems { get { return x < 10000; } } //maximum
//the count is the number requested
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return AsyncInfo.Run(async cancelToken =>
{
//here you need to do your loading
for (var c = x; c < x + count; c++)
{
//add your newly loaded item to the collection
Add(new Item()
{
Text = c.ToString()
});
}
x += count;
//return the actual number of items loaded (here it's just maxed)
return new LoadMoreItemsResult { Count = count };
});
}
}
We are using a new Item class, so lets define it:
//the type which is being used to display the data
//you could extend it to use images and stuff
public class Item
{
public string Text { get; set; }
}
Lets create a viewmodel:
public class ViewModel
{
public ViewModel()
{
Items = new IncrementalLoadingCollection();
}
//using the custom collection: these are your loaded items
public IncrementalLoadingCollection Items { get; set; }
}
Wrap it up in the code behind: we use the data context:
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext= new ViewModel(); //using a viewmodel as data context
}
}
More info can be found here
Currently, I have a project to display nearby places with UWP using Google Places API. I'm using ListView to display the numbers of nearby places and did successfully to display some basic information provided by API into my ListView. I have DataTemplate like this
<DataTemplate x:Key="ResultPlaces">
<StackPanel Orientation="Vertical">
...
<Grid Grid.Column="2">
<TextBlock Text="{Binding placeDistance}" Foreground="#42424c" TextWrapping="Wrap" FontSize="12" Margin="10,0,0,0"/>
</Grid>
</Grid>
</StackPanel>
And i also have the ListView like this
<ListView Name="listPlace"
ItemTemplate="{StaticResource ResultPlaces}">
</ListView>
I've parsing the JSON in result of API in code behind and make it to be my ListView.ItemsSource. The problem is the API don't provide distance object. So, I created a method to calculte distance between 2 places and use it to calculate every single result in API results. I'm also created get set property called placeDistance in class Result that provided API items result.
This my get set property
public class Result
{
....
public Review[] reviews { get; set; }
public int user_ratings_total { get; set; }
public string placeDistance { get; set; }
}
And this my code to calculate every single distance on Result
int lengthResult = placesList.results.Count();
for (int i = 0; i < lengthResult; i++)
{
double myLat = placesList.results[i].geometry.location.lat;
double myLong = placesList.results[i].geometry.location.lng;
double myCurrentLat = Convert.ToDouble(parameters.lat);
double myCurrentLong = Convert.ToDouble(parameters.longit);
var newDistance = new Result();
newDistance.placeDistance = DistanceBetweenPlaces(myCurrentLong, myCurrentLat, myLong, myLat);
}
When I deployed it into my phone, the other items in DataTemplate display correctly. But I couldn't get any distance text. Did I do something wrong ?
It's difficult to say for certain without seeing the full code, but my best guess is that you shouldn't create a new instance of Result in the loop, where you are calculating the distance.
Judging from the naming, I would assume that placesList.results is already a list of Results with all the other items, which you are binding to the ListView. In this case, you should replace:
var newDistance = new Result();
newDistance.placeDistance =
DistanceBetweenPlaces(myCurrentLong, myCurrentLat, myLong, myLat);
with:
placesList.results[i].placeDistance =
DistanceBetweenPlaces(myCurrentLong, myCurrentLat, myLong, myLat);
Simple question but I am totally confused. I am developing a wp7 app using C#. I want a listbox with input number of image item which source should be same i.e. the list box should contain 'n' Image control with source set to a single image where 'n' is number of Listbox Item enter by user. e.g. If the user input '10', then the listbox should have ten items. I want the listbox ItemsPanelTemplate as Wrap-panel. Can somebody suggest me how to get this?
Define a ListBox in your XAML something like this
<ListBox x:Name="ListBoxImages">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Imagesource}" Width="300"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and then set its Source in the code behind like this
int noOfImages = 10; //Take the input from user
List<ImageClass> imageList = new List<ImageClass>();
for(int i=0; i<noOfImages; i++)
imageList.Add(new ImageClass() { Imagesource = "/user.jpg" });
ListBoxImages.ItemsSource = imageList; //Set the source of the listbox here
where ImageClass is,
public class ImageClass
{
public String Imagesource { get; set; }
}
The above is a sample for your understanding. Please customize wisely to suit your needs
I am developing an application where I have a class called UIManager in which there is a method which has an array of data
public void DisplayCatalog(string[] displayName, BitmapImage[] icons)
{
DisplayItem.Clear();
for (int i = 0; i < displayName.Length; i++)
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i] });
}
NotifyPropertyChanged("UI");
}
Now I want this data ie;WidgetName to be displayed in my MainPage where I have used a Looping selector.
*<custom:LoopingSelector x:Name="selectorLeft" ItemMargin="5" ItemSize="145,145" Margin="6,0,-6,22">
<custom:LoopingSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding WidgetName}"/>
</StackPanel>
</DataTemplate>
</custom:LoopingSelector.ItemTemplate>
</custom:LoopingSelector>
*
Also I need to scroll the looping selector Horizontally.
How can I achieve this...??? Any valuable solutions Please.......
I have used Horizontal Looping Selector but I am not getting how to bind the data from my UIManager class on to the Horizontal Looping Selector..
<toolkit:HorizontalLoopingSelector Grid.Row="0" Margin="12" Height="128" ItemSize="128,128" ItemTemplate="{StaticResource ?????}">
<toolkit:HorizontalLoopingSelector.DataSource>
????????
</toolkit:HorizontalLoopingSelector.DataSource>
</toolkit:HorizontalLoopingSelector>
u need to create a datasource class and bind the values to the looping selector source .its similar to binding source to the vertical looping selector.
Visit http://www.windowsphonegeek.com/articles/WP7-LoopingSelector-in-depth--Part1-Visual-structure-and-API
Check out this resource if you still require a horizontal loop selector:
http://blog.supaywasi.com/2011/06/horizontal-looping-selector/