I'm writing a silverlight app and I'm trying trying to improve the loading time.
When my page loads, I first initialize my ObservableCollection:
this.MyItems = new ObservableCollection<Item>();
My UI is a ListBox which I bind to an ObservableCollection through code. In MainPage_Loaded:
MyList.ItemsSource = App.ViewModel.MyItems;
Now I bind the UI to my model. I expect this to be efficient as the collection is empty, and the rest of the UI can continue to load (not sure if my assumption is correct).
DataContext = App.ViewModel;
Now I want to add items to my collection:
for (int i = 0; i < number_of_items; i++)
{
this.MyItems.Add(myItems[i]); // myItems is a List<Item> already populated
Thread.Sleep(20);
}
My goal was to let the thread sleep so that it would have time to render the UI for each list box item. Also, I expected my UI to display one item at a time.
The result is that the ListBox elements appear altogether at once. If I set a Sleep of 1 second, the ListBox gets populated after 1 second times the number of elements.
What's the good way of optimizing this operation? If it's futile, I may also just bind my ListBox to a fully populated ObservableCollection. Thanks!
Try moving the loop to a background thread. Here is one way to do that.
Phạm Tiểu Giao - Threads in WP7
Note you'll need to dispatch the UI update. Something like
Dispatcher.BeginInvoke( () => { this.MyItems.Add(myItems[i]); } );
Sleep will work if you want to use a fixed time period. Just make sure the time period is always longer than the time taken to update the display, or you will potentially overload the UI thread with updates faster than it can process.
The reason we bind to an ObservableCollection is so the built-in notification occurs of property updates through the INotifyPropertyChanged interface implementation. This causes an event to be fired on each update to the underlying collection, which in turn causes a redraw of the related UI element(s) (ListBox in this case). The data template is applied on each redraw to each item in the collection, and is automatically done so through data binding. The items are being added to your collection faster than the draw can take place (on a separate thread), hence why it appears your load is delayed until all items are added. Youre missing the redraw cycles visually on the screen since theyre being drawn and invalidated on the screen when new items are added.
This means that your Thread.Sleep call is only delaying the complete redraw of the element on each item that is added (* number of items being added explains why your UI is being redrawn entirely on each item, but only after all have had their respective Thread.Sleep calls made which blocks the UI thread for n * sleepValue time). This is why we need to use a Dispatcher object as indicated above as these calls are made on a different thread. This allows us to redraw from the UI thread, which in essence, synchronizes the blocking calls.
I would absolutely not use the Dispatcher here as it is redundant and prevents the native synchronization to occur since the dispatcher could be referenceing an element that has not yet been created and added to the visual or logical trees (as clearly experienced by your comment on settign the value to 1000ms versus 20ms). You will still be redrawing all items in the collection as each is added making your sleep call invalid, or nonfunctional, for lack of a better term.
What I offer as an alternative solution is that you could add a storyboard animation on the Opacity property of the root element of your data template to create the visual effect of the items doing something "one at a time as being added." This way, as each item is added to the underlying collection, they will be drawn with the opacity fade animation, giving the illusion that each item is being added one at a time (and animating into view) with separate animations (at different offsets within the defined animation). I believe this will give you the effect youre looking for. But since the draw call comes from ListBox as it maintains its collection of items, the entire collection will be invalidated on each .Add call to your ViewModel items ObservableCollection object. There really is no way to override this behavior since it happens one level upstream in the hierarchy. I would advise against the provided approach.
Related
I have a scrollview and table view in one screen. Both of them contain heavy data as its a trading app. So every second I receive and update in the value and call cell update. Meanwhile if the user tries scrolling the scrollview,it doesnt respond, though the scroll delegates are called.
Any help will be appreciated. Thanks
You should try to investigate two things:
When you say you have a table view and a scroll view, I hope you are not putting your table view inside a scroll view, and increasing the content height of scroll view and table view. If you do this, you will be making multiple cells, and loading all the cells in memory. This technique doesn't go well with dequeueReusableCellWithReuseIdentifier.
Only do the UI update on main queue, calling and receiving data should not be done on main queue.
I suspect that you may be doing something wrong in point 1. How many cells would you show at one time on an iPhone/iPad screen, max 10-15 cells. If you reuse cells and have 10-15 cells in memory, your app won't go slow.
I have three longlistselector within a pivot control.
Each longlistselector has between 10-20 items.
When I navigate back to the page that displays the pivot + longlistselector, the page takes about 3 seconds to render on a Nokia Lumia with 512 MB.
I took a performance analysis within Visual Studio 2013 and noticed that a frame had about 85% CPU utilization. Digging into the visual tree showed ~70% rendering time for the item presenter of the pivot. This is then split up to 35%, 16%, 20% for each of the LongListSelectors within the pivot control.
When I expand the ItemPresenter, I can see the LongListSelector consuming most of the time. Below, I can see "ContentPresenter" and Canvas which also take the time.
I took a Memory analysis for the same but the tool could not find anything suspicious there.
How can I check what exactly takes so long to re-render a page which was previously displayed?
I had the same issue, and it appears to simply be that the LongListSelector has to re-render the contents of your view model when it returns. The only way I could find to improve the situation was to un-hook the ItemSource property of the LLS in the page "OnNavigatedTo" method, and then re-hook it back up programatically in the page "OnLoaded" event (see below for an example)
Unhook:
MyLongListSelectorControl.ItemsSource = null;
Recreate:
Binding ItemSourceBinding = new Binding("MyDataProperty");
MyLongListSelectorControl.SetBinding(LongListSelector.ItemsSourceProperty, ItemSourceBinding);
Note that the "MyDataProperty" can be exactly the text you used to bind in XAML.
This way the page at least can visually render completely with some kind of "loading" indicator and then the longlist selector will be populated afterwards, giving the illusion of the app responding perfectly.
I am new to Windows Phone Programming. In my application, I Have a listbox which lists the phone contacts. Currently it is listed as a regular list with equal size for list items in the UI.I am looking to modify the front end like this :
I dont want to have different sizes /back ground color for each list items rather a fixed UI and let the lists scroll through it and the list item in the view, at any time, should be displayed as in the picture.
I dont expect any codes as answers but any examples are welcome too, just want to know using what feature this kind of functionality is possible so that i can do my reading!
Thanks,
Deepak
It would be difficult to modify an existing control (ListBox for example) to act like this, so your best bet would probably be an ItemsControl with its RenderTransform set as a TranslateTransform.
If you place a Rectangle (with Fill="Transparent") over the ItemsControl, you can attach handlers to the ManipulationStarted, ManipulationDelta, and ManipulationCompleted events to control the "scrolling" by setting the TranslateTransform's Y offset.
To resize the items in the list there are two options: a custom panel or manual setting.
Custom Panel
You could create a custom Panel implementation that will appropriately resize its Children based on a property you would create to represent the scroll position. Set the ItemsControl to use your panel, and either through binding or attaching a handler to the panel's Loaded event and keeping a pointer to the panel, update the aforementioned property from inside the ManipulationDelta handler.
Manual Setting
From inside of the ManipulationDelta handler, you can also calculate the various heights of the boxes and use MyItemsControl.ItemContainerGenerator.ContainerFromIndex to get the visual for each item and set its height.
I would suggest putting this all inside of a custom UserControl.
You may have issues with clipping using the TranslateTransform but hopefully this will get you started. Unfortunately, this looks like a rather difficult control to try making as your first windows phone project!
So Finally I did manage to find a way to do it.
First approach was as #Murkaeus suggested, Using UserControl and ManipulationDelta event handler. However for some reason the manipulationDelta event was triggered only for 2 finger gestures (Zoom, Pinch..etc), I have no clue why. And after some trying I had to give up on this.
The next approach was using Listbox itself. The source of the Listbox was set as the List( of Models objects) that I create after reading the contact information from phone. The height and color of the listbox item was bound to a property in my model named "scaleLevel" and was accordingly converted by implementing IValueConvertors to give predefined color and height values for different scale levels.
I created an attached property for the scrollviewer vertical offset like mentioned [here] (http://www.scottlogic.co.uk/blog/colin/2010/07/exposing-and-binding-to-a-silverlight-scrollviewer%E2%80%99s-scrollbars/)!
This event is triggered on change of the vertical offset and every time there is a scroll, I find out which listbox item to be enlarged and which reduced based current vertical offset.
Once I have this information, I change the property ("scaleLevel") of the affected items in the List (of Model) (which is bound to listbox height and color). This change is updated in UI using the INotifyPropertyChanged Event.
I have no idea if this the best way of doing it , but it works well and there is no considerable in updating the UI despite the processing involved.
I would like to hear your opinion about the implementation and any other solution which you feel will work better.
I am using MonoMac to build a desktop download manager for Mac in C#.
My XIB has a Table View, whose columns are bound to an NSArrayController. The array controller is connected to my Main Window Controller through an IBOutlet. The array holds a bunch of HttpDownload objects, which derive from NSObject. These HttpDownload objects contain properties such as TotalSize, TotalDownloaded, Bandwidth, etc. I have decorated these properties with an [Export] attribute.
In the controller I add some HttpDownload objects to the NSArrayController using the AddObject method. A background process, started with Task.Factory.StartNew() begins the download asynchronously and updates the bound properties such as TotalDownloaded and Bandwidth as data is received.
I can see these new values being reflected in the Table View, but only once I've "forced" a UI update, for instance by causing the window to lose focus, gain focus, or by clicking on a button within the window.
I have tried setting Continuously Updates Value in IB, but this makes no difference (and reading the docs, I didn't think it should).
Does anyone know to make the UI update the bound values in "real time", instead of only when a window event occurs?
I figured this out shortly after I posted this question.
It seems that we need to manually call WillChangeValue() and DidChangeValue() for at least one of the keys that are being updated, for instance, when I updated the total downloaded:
WillChangeValue("DownloadedBytes");
DownloadedBytes += bytesRead;
DidChangeValue("DownloadedBytes");
In my case, calling these methods for just one of the updated keys seems to be enough to force an update of all the bound values.
For reference, in Objective-C these selectors are called [self willChangeValueForKey:#"keyname"] and [self didChangeValueForKey:#"keyname"].
I have a peculiar problem in Windows Phone Development. I have 4 panorama items each of them containing a webBrowser control. On the start of the application, I have only the first panorama item visible while the remaninig are in collapsed state.
Based on the interaction in the first webBrowser, we Notify the WP7 app (webBrowser.ScriptNotify event) and decide which panoramaitems to display. The visiblity is set in the delegate that handles the ScriptNotify event.
the issue I am facing is that though i set the visibility in the delegate to Visible, it doesn't show up in the Panorama. I have tried using Dispatcher in the delegate to change visibility but it hasn't helped:
Deployment.Current.Dispatcher.BeginInvoke(() => {
discussions.Visibility = System.Windows.Visibility.Visible;
});
Can someone suggest what i could be doing incorrectly?
First of all, you shouldn't use a WebBrowser control inside a Panorama. It's very bad performance.
Secondly, Panorama and PivotItems don't have a collapsed state.
And thirdly, the dispatcher have nothing to do with this (unless you're not running the code on the UI thread).
So what you need to do, is to add the PanoramaItems dynamically to the Panorama control. This can be done by databinding (recommended), or directly from C#.
I don't know about performace but i'm sure that a PanoramaItem has a collapsed *visibility* state i tried to toggle it from code and it works like a charm if the initial state is visible.
But if the initial state is collapsed when the panorama loads then it doesn't work anymore. I guess it is because if it is collapsed then it is not included in the panorama and so it won't be visible when you set it to visible.
Maybe that's a bug or i don't know but it is a bit awkward.
To add the PanoramaItem to the Panorama could work.
I'm sorry for not answering the question but the other way around, enforcing the problem :) .
I do have the same problem, one PanoramaItem with an ItemsControl databounded to a collection of the ViewModel. The PanoramaItem visibility property is databounded to {Binding} and a CollectionToVisibility converter is used. During debug with a breakpoint set inside the converter code I managed to see that the return value is ok but the PanoramaItem is not visible when the collections has items.
My investigation took me to realize that in fact when the first get to the Collection occours the return value is null because the Collection data comes from a async call to a service and the converter return value is Visibility.Collapsed, and only when the Collection is populated and the PropertyChanged event is raised, the second get to the Collection property triggers the databind refresh and the return value of the converter is now Visibility.Visible, this leeds me to think that the PanoramaItem isn't included in the Panorama control tree during the applytemplate because the visibility is set to collapsed, and after that the UI never loads the PanoramaItem again.
I made a test to verify this scenario returning in the get of the Collection property an hardcoded list of items so that the first get have items in it and the converter returns Visible in the first get request.Everything works like a charm.I can even string the collection out of items and it gets collapsed and the other way around.
All this is pure Xaml, no code behind.
The intent of this is to hide PanoramaItems that for some reason does not have content to show.
Basically, the panorama keeps an internal list of visible item. This list isn't updated when you set an item to visible. The easiest way to force the control to update this list is setting the MainItem property. So after setting the visibility of your panorama item, just add the following line:
yourPanorama.DefaultItem = yourPanorama.DefaultItem;
(given that the panorama is called yourPanorama)