I am looking for an event (or another mechanism / workaround) that I can use to know when rendering of trading cards has completed in the Pivot Viewer control. I am using the SL5 version of the control and adding to its ItemSource (via an observable collection). Currently, there is quite a lag between providing the control with data and seeing something animate on the pivot viewer control. My thread that populates ItemSource exits and after 5-10 seconds of staring at a blank screen, the user eventually sees the trading cards images. The built-in events don't support any indication of when rendering of the trading card images is in a 'ready' state.
I had the same issue so I coded up my own solution by extending the pivotviewer control: http://stevenhollidge.blogspot.ch/2012/12/pivotviewer-itemsloaded-event.html
I hope someone finds this useful!
I looked into this (talking to the PivotViewer developers) and there is currently nothing you can handle to figure out when rendering has completed.
The best option open to you may be to investigating the SL rendering performance and look for the drop after loading a collection. Not pretty and might not work anyway...
Starting point: http://msdn.microsoft.com/en-us/library/bb980092.aspx
Jason R. Shaver
Previous PM for PivotViewer
The best way I've found to detect when the visuals are loaded is to find the MultiScaleImage object and detect whether the images are "Downloading" or "Idle", as well as what the viewport of an image is:
Here's how you can get at that object on the PivotViewer in SL5:
Subclass the PivotViewer object and place the following in OnApplyTemplate() override:
PartContainer = (Grid)this.GetTemplateChild("PART_Container");
cvv = (PartContainer).Children[2] as CollectionViewerView;
if (cvv != null)
{
cvvm = ViewBehaviors.GetViewModel(cvv);
Grid container = cvv.Content as Grid;
Border viewerBorder = container.Children[1] as Border;
Grid cvGrid = viewerBorder.Child as Grid;
cvc = cvGrid.Children[0] as CollectionViewContainer;
}
Then you have a reference to cvv - the CollectionViewerView.
When you set your ItemsSource on the PivotViewer object, start a timer that will check every 300 ms or so for this:
ItemViewerView ivv = ((Grid)(((UserControl)(cvc.Content)).Content)).Children[0] as ItemViewerView;
Grid g = (((Grid)ivv.Content).Children[0] as Grid);
ContentControl cc1 = (g.Children[0] as ContentControl);
if (cc1 != null)
{
Canvas cvs = cc1.Content as Canvas;
if (cvs != null && cvs.Children.Count > 0)
{
var contentControl = cvs.Children[0] as ContentControl;
if (contentControl != null)
{
MultiScaleImage x = contentControl.Content as MultiScaleImage;
bool isIdle = x.Source != null && !x.IsDownloading && x.IsIdle;
// This could be more precise, but the origin is by default set to 99999 when a new image is loaded in - we're watching for when this value changes.
bool inViewPort = x.SubImages[x.SubImages.Count - 1].ViewportOrigin.X < 999999;
// if both of these boolean values are true, then the images will be displaying on the screen.
}
}
}
Note this is SL .dll with version 5.0.61118 (future version this code will most likely break)
Related
I am looping through each item in a listview and formatting the text boxes in the grid view. The initial format works, but then formatting disappears if I scroll the listview up and down a couple of times.
This is the basic loop to work through my items:
for (int i = 0; i < lvDocumentation.Items.Count; i++)
{
// Grabs the item
ListBoxItem selectedListBoxItem = lvDocumentation.ItemContainerGenerator.ContainerFromItem(lvDocumentation.Items[i]) as ListBoxItem;
PersonDTO thisPersonDTO = (PersonDTO)lvDocumentation.Items[i];
// I do some odds and ends here and call formatting routines for the buttons...
}
Later after I do some checking on thisPersonDTO, I format the selectedListBoxItem depending on the persons status
// Textblock
foreach (var item in UiCommon.FindVisualChildren<TextBlock>(selectedListBoxItem))
{
if (item.Name.ToLower() == textblockName.ToLower())
{
if (docPresent)
{
//item.Background = backgroundColor;
item.Foreground = Brushes.Green;
}
else
{
((TextBlock)item).Foreground = Brushes.Red;
}
}
}
// Button
foreach (var item in UiCommon.FindVisualChildren<Button>(selectedListBoxItem))
{
if (item.Name.ToLower() == buttonName.ToLower())
{
if (item.GetType().Name == "Button")
{
if (docPresent)
{
//item.Background = backgroundColor;
item.IsEnabled = true;
}
else
{
//item.Background = backgroundColor;
((Button)item).IsEnabled = false;
}
}
}
}
That code works and the formatting is done correctly (as long as the listBoxItem is in the display, but that's another story)
Once the process is completed button colors are set.
If I scroll up and down a bit the colors revert to their original formatting and I can't figure out why. I have breakpoints everywhere, but don't see any code executing.
I realize that there are formatting issues when I try to format listboxitems that are not yet displayed. I use this bit of code for that:
lvDocumentation.UpdateLayout();
lvDocumentation.ScrollIntoView(lvDocumentation.Items[index]);
selectedListBoxItem = lvDocumentation.ItemContainerGenerator.ContainerFromItem(lvDocumentation.Items[index]) as ListBoxItem;
That gets me the listbox item rather than null if the item originally returned null because it was not in the display area.
Something seems to be refreshing the formatting, but no clue why / where?
If I scroll up and down a bit the colors revert to their original formatting and I can't figure out why.
It's because of the default UI virtualization and container recycling that is described in the docs.
You may disable the virtualization by setting the attached IsVirtualizing property to false:
VirtualizingPanel.SetIsVirtualizing(lvDocumentation, false);
This comes at the expense of the scrolling performance but given your current approach of manipulating the UI containers directly, that's your only option.
You should look into MVVM. It's the recommended design pattern to use when developing XAML based UI applications.
Is it possible to implement smooth scroll in a WPF listview like how it works in Firefox?
When the Firefox browser contained all listview items and you hold down the middle mouse button (but not release), and drag it, it should smoothly scroll the listview items. When you release it should stop.
It looks like this is not possible in winforms, but I am wondering if it is available in WPF?
You can achieve smooth scrolling but you lose item virtualisation, so basically you should use this technique only if you have few elements in the list:
Info here: Smooth scrolling on listbox
Have you tried setting:
ScrollViewer.CanContentScroll="False"
on the list box?
This way the scrolling is handled by the panel rather than the listBox... You lose virtualisation if you do that though so it could be slower if you have a lot of content.
It is indeed possible to do what you're asking, though it will require a fair amount of custom code.
Normally in WPF a ScrollViewer uses what is known as Logical Scrolling, which means it's going to scroll item by item instead of by an offset amount. The other answers cover some of the ways you can change the Logical Scrolling behavior into that of Physical Scrolling. The other way is to make use of the ScrollToVertialOffset and ScrollToHorizontalOffset methods exposed by both ScrollViwer and IScrollInfo.
To implement the larger part, the scrolling when the mouse wheel is pressed, we will need to make use of the MouseDown and MouseMove events.
<ListView x:Name="uiListView"
Mouse.MouseDown="OnListViewMouseDown"
Mouse.MouseMove="OnListViewMouseMove"
ScrollViewer.CanContentScroll="False">
....
</ListView>
In the MouseDown, we are going to record the current mouse position, which we will use as a relative point to determine which direction we scroll in. In the mouse move, we are going to get the ScrollViwer component of the ListView and then Scroll it accordingly.
private Point myMousePlacementPoint;
private void OnListViewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
myMousePlacementPoint = this.PointToScreen(Mouse.GetPosition(this));
}
}
private void OnListViewMouseMove(object sender, MouseEventArgs e)
{
ScrollViewer scrollViewer = ScrollHelper.GetScrollViewer(uiListView) as ScrollViewer;
if (e.MiddleButton == MouseButtonState.Pressed)
{
var currentPoint = this.PointToScreen(Mouse.GetPosition(this));
if (currentPoint.Y < myMousePlacementPoint.Y)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - 3);
}
else if (currentPoint.Y > myMousePlacementPoint.Y)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + 3);
}
if (currentPoint.X < myMousePlacementPoint.X)
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 3);
}
else if (currentPoint.X > myMousePlacementPoint.X)
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + 3);
}
}
}
public static DependencyObject GetScrollViewer(DependencyObject o)
{
// Return the DependencyObject if it is a ScrollViewer
if (o is ScrollViewer)
{ return o; }
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
{
var child = VisualTreeHelper.GetChild(o, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
There's some areas it's lacking as it's just a proof of concept but it should definitely get you started in the right direction. To have it constantly scroll once the mouse is moved away from the initial MouseDown point, the scrolling logic could go into a DispatcherTimer or something similar.
Try setting the ScrollViewer.CanContentScroll attached property to false on the ListView. But like Pop Catalin said, you lose item virtualization, meaning all the items in the list get loaded and populated at once, not when a set of items are needed to be displayed - so if the list is huge, it could cause some memory and performance issues.
try setting the listview's height as auto and wrapping it in a scroll viewer.
<ScrollViewer IsTabStop="True" VerticalScrollBarVisibility="Auto">
<ListView></ListView>
</ScrollViewer>
Don't forget to mention the height of ScrollViewer
Hope this helps....
I know this post is 13 years old, but this is still something people want to do.
in newer versions of .Net you can set VirtualizingPanel.ScrollUnit="Pixel"
this way you won't lose virtualization and you get scroll per pixel instead of per item.
In our app, we constantly reload objects from the server and then place them inside a recyclerview scrollview. Now, it does work fine but it is a big laggy. Like - it scrolls, then stops and reloads for a second, then scrolls, then reloads and so on and so forth. I have noticed that other apps do this differently. For instance, when one scrolls thorugh facebook. One can scroll all the way down and then, when the images have done loading, they get placed inside their empty layout container. So I thought this is only achievable via background threadding - so I have done that but the result is the above mentioned. It is just nowhere near as smooth as it is with facebook or any other app where you scroll throuh images. What could be the problem here:
private async Task LoadTHEBOOKAsync(PVH_TheBook_Singles vh, int position, int fkTaskId)
{
await Task.Run(() =>
{
//Loading on another Thread
int taskID = loadPhotos.pictures[position - (this.hasHeader ? 1 : 0)].taskID;
int taskIDWHenTheBook = loadPhotos.pictures[position - (this.hasHeader ? 1 : 0)].fullscreenID;
Bitmap bmp = loadImage(position);
String commentCount = KumulosHelper.Functions.Pictures.GetCountOfComments
(fkTaskId.ToString()).ToString();
String LikeCount = loadPhotos.pictures[position - (this.hasHeader ? 1 : 0)].likes.ToString();
int taskDoneId = loadPhotos.pictures[position - (this.hasHeader ? 1 : 0)].taskdoneid;
String exp = null;
if (taskID != 0)
{
exp = BookOfLife.Challenges.Libary.Instance.getTask(taskID).Experience.ToString() + "ap";
}
else
{
exp = BookOfLife.Challenges.Libary.Instance.getTask(taskIDWHenTheBook).Experience.ToString() + "ap";
}
// Setting on UI Thread
((Activity)ctx).RunOnUiThread(() =>
{
vh.Image.SetImageBitmap(bmp);
vh.txtComments.Text = commentCount;
vh.txtLikeCountBlue.Text = LikeCount;
vh.taskdoneid = taskDoneId;
vh.txtap.Text = exp;
});
});
}
As you can see, all the donwloading is done in an async task and only the placing is done on the ui thread. Is this wrong? Because I cannot seem to find any other solution to this ? I mean, I do have to set my images on the UI thread, right? So i HAVE to block it for a second until everything sits right, dont I?
Any help would be awesome.
Thanks! :)
You can check the WoWonder Android Timeline which is built in xamarin and you get more info there about the news feed system
enter image description here
I'm using iCarousel by #Nick Lookwood to load a scrollable list of "flashcards".
Each View of the iCarousel control is one flashcard. By design, I require that when a user taps the flashcard (view), the flashcard flips to reveal the behind. Before using iCarousel, I was making two separate controls, one for front and one for back, and then using UIView.Transition (with a nice Flip From Top animation) to go from front to back when a Tap was detected, or the other way round.
Adding a UITapGestureRecognizer to my View is leading to weird artifacts and not functioning as expected (overlapping controls, the next one instead of the current one flipping, no animation, etc.) and I need a different approach. I could conveniently use the Selected event in the iCarousel Delegate instead of a Tap Gesture Recognizer, but what do I do there exactly?
In essence, I would like to replace the specific view which was tapped with another but I feel this is conflicting with the whole reusable views idea. Is there nothing I can do? (Once the view is out of the screen, I am fine with it being "flipped forward" again.)
Thanks!
p
P.S. I'm using C# and Xamarin.iOS, but I can understand Obj-C and Swift code fairly well, so any help will be appreciated.
There are lots of example for iCarausal. I assume you are using this :
iCarausal
and specifically folder name "Basic iOS Example"
How about the below changes in delegate methods:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
UILabel *label = nil;
//create new view if no view is available for recycling
if (view == nil)
{
//don't do anything specific to the index within
//this `if (view == nil) {...}` statement because the view will be
//recycled and used with other index values later
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)];
((UIImageView *)view).image = [UIImage imageNamed:#"page.png"];
view.contentMode = UIViewContentModeCenter;
//back view i have just added this apart from it there is no change made by me in this method
UIView *backView = [[UIView alloc] initWithFrame:view.bounds];
backView.tag = 2;
[backView setBackgroundColor:[UIColor redColor]];
[view addSubview:backView];
[backView setHidden:YES];
//back view
label = [[UILabel alloc] initWithFrame:view.bounds];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentCenter;
label.font = [label.font fontWithSize:50];
label.tag = 1;
[view addSubview:label];
}
else
{
//get a reference to the label in the recycled view
label = (UILabel *)[view viewWithTag:1];
}
//set item label
//remember to always set any properties of your carousel item
//views outside of the `if (view == nil) {...}` check otherwise
//you'll get weird issues with carousel item content appearing
//in the wrong place in the carousel
label.text = [_items[index] stringValue];
return view;
}
and implementing didSelect delegate as below:
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index{
UIView *frontview = [carousel itemViewAtIndex:index];
UIView *backView = [frontview viewWithTag:2];
BOOL isInFront = NO;
if (backView.hidden == NO) {
isInFront = YES;
}
[UIView transitionWithView: isInFront ? backView : frontview
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[backView setHidden:isInFront];
}
completion:nil];
}
You should write your code more structured, it was an example to show you that you can acheive what you want with iCarausal.
I am using Xamarin and C# but I suspect the problem is equally valid in a Java environment.
I have an ActionBar Activity that hosts three tabs each of which hosts a fragment. It uses a ViewPager to allow the user to swipe between the tabs.
The requirement is to programmatically screenshot each tab and then email these as attachments.
The problem is that whilst the ActionBar/ViewPager works well it also optimises the tabs - effectively it isn't creating a fragment's view until it is next in line to be shown. So, if you're on tab 0 - the first tab - then the fragment view for tab 2 is null. So it can't be screenshot.
To overcome this I have tried to set any tab/fragment that has a null view to be selected. This generates the view but because setting it to be selected does not actually render it on screen the view does not have a width or a height value so again it cannot be screenshot (this is the reason for the defensive check at the start of the code taking the screenshot).
So, I guess my question is how can I force the tab to be rendered on screen so that it is correctly filled out and can be screenshot?
My main code extracts are as follows:
private void EmailReport()
{
List <Bitmap> bitmaps = new List<Bitmap>();
List <string> summaryFiles = new List<string>();
// remember the tab we're on
var selectedTab = this.ActionBar.SelectedNavigationIndex;
// take the screenshots
for (int fragmentNumber = 0; fragmentNumber < projectFragmentPagerAdapter.Count; fragmentNumber++)
{
Android.Support.V4.App.Fragment fragment = projectFragmentPagerAdapter.GetItem(fragmentNumber);
if (fragment.View == null)
{
this.ActionBar.GetTabAt(fragmentNumber).Select();
fragment = projectFragmentPagerAdapter.GetItem(fragmentNumber);
}
bitmaps.Add(ScreenShot(fragment.View));
}
// set the active tab back
this.ActionBar.GetTabAt(selectedTab).Select();
//write the screenshots into file
int i = 0;
foreach(Bitmap bitmap in bitmaps)
{
if (bitmap != null)
summaryFiles.Add(BitmapToFile(bitmap, this.ActionBar.GetTabAt(i).Text));
i++;
}
// now send the file
EmailSupport.SendAttachments(this, summaryFiles);
}
private Bitmap ScreenShot(View fragmentRootView)
{
if (fragmentRootView == null || fragmentRootView.Width == 0 || fragmentRootView.Height == 0)
return null;
fragmentRootView.DrawingCacheEnabled = true;
//create a bitmap for the layout and then draw the view into it
Bitmap bitmap = Bitmap.CreateBitmap(fragmentRootView.Width, fragmentRootView.Height,Bitmap.Config.Argb8888);
Canvas canvas = new Canvas(bitmap);
//Get the view's background
Drawable bgDrawable = fragmentRootView.Background;
if (bgDrawable!=null) // has background drawable, then draw it on the canvas
bgDrawable.Draw(canvas);
else // does not have background drawable, then draw white background on the canvas
canvas.DrawColor(Color.White);
// draw the view on the canvas
fragmentRootView.Draw(canvas);
fragmentRootView.DrawingCacheEnabled = false;
return bitmap;
}
Any help would be gratefully received.
The solution in the end was very simple. The ViewPager has a setting controlling the number of pages (fragments) that it will hold "activated". This defaults to 1. As I had 3 tabs this meant there was always one tab (fragment) out of reach.
So, whilst setting up the ViewPager do the following before the tabs are added:
reportViewPager.OffscreenPageLimit = pageCount - 1;
Or in Java
reportViewPager.setOffscreenPageLimit(pageCount - 1);
I hope this helps someone else avoid wasting hours.