Screenshots of multiple actionbar tabs - c#

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.

Related

Custom Accepted Operation in Drag and Drop

I am making a library management software. This is the screenshot:
I have implementing functionalities like dragging a book into the delete icon to delete the book. But there are two obstacles:
DataPackageOperation has only four possibilities: Copy, link, move none. So, after four, it is difficult to differentiate which AppBarButton is the book dropped on.
I plan to add more items to the CommandBar. But there are only four possible operations
I need a way to give the user a custom feedback as to which AppBarButton is the book currently dragged over. DataPackageOperation contains only four. Out of which, 'None' cannot be used(because it will be confusing). Is there a way to provide that feedback?
I need a way to give the user a custom feedback as to which AppBarButton is the book currently dragged over
You could give the user a custom feedback via custom Drag UI. The follow code comes from XamlDragAndDrop official code sample.
private void TargetTextBox_DragEnter(object sender, Windows.UI.Xaml.DragEventArgs e)
{
/// Change the background of the target
VisualStateManager.GoToState(this, "Inside", true);
bool hasText = e.DataView.Contains(StandardDataFormats.Text);
e.AcceptedOperation = hasText ? DataPackageOperation.Copy : DataPackageOperation.None;
if (hasText)
{
e.DragUIOverride.Caption = "Drop here to insert text";
// Now customize the content
if ((bool)HideRB.IsChecked)
{
e.DragUIOverride.IsGlyphVisible = false;
e.DragUIOverride.IsContentVisible = false;
}
else if ((bool)CustomRB.IsChecked)
{
var bitmap = new BitmapImage(new Uri("ms-appx:///Assets/dropcursor.png", UriKind.RelativeOrAbsolute));
// Anchor will define how to position the image relative to the pointer
Point anchor = new Point(0,52); // lower left corner of the image
e.DragUIOverride.SetContentFromBitmapImage(bitmap, anchor);
e.DragUIOverride.IsGlyphVisible = false;
e.DragUIOverride.IsCaptionVisible = false;
}
// else keep the DragUI Content set by the source
}
}

Auto Scroll Table Layout Panel does not start at top

I am working on a very simple table layout application for getting started with learning C#. I am doing everything programmatic ally ( not through design editor)
I am trying to add scrolling onto the application. It seems to work fine, but it does not seem to start at the top of the horizontal range by default. I tried adding things like Max/min size, autoscroll margins etc., but nothing seems to have the desired effect. I am sure there is something simple I am missing.
Here is my current code as it relates to the problem.
layout = new TableLayoutPanel();
layout.Height = 1075;
layout.Width = 704;
layout.Name = "masterLayout";
layout.Dock = DockStyle.Fill;
layout.AutoScroll = true;
int i = 0;
foreach (Race r in ELECTION_DATA.races.OrderBy(o => o.race_id)) {
layout.Controls.Add(new Label { AutoSize = true, Text =r.race_id, Name=r.race_id, Width=300}, i, 0 );
layout.Controls.Add(new TreeView { AutoSize = true, Text = r.race_id, Name = r.race_id, Height = 1000, Width = 300 }, i,1);
i += 1;
}
Controls.Add(layout);
Here is an image, The Label Control Is not visible because the scroll is offset to the beginning of the tree view.
How can I ensure the scroll always starts at the very top?
The ScrollLayoutPanel has a method called ScrollControlIntoView that will move a specific control inside the panel into view. If you just scroll your first control into the view after you are done filling your panels, then that should ensure that the top is visible. In other words:
// do your loop first...
foreach (...)
{
layout.Controls.Add(...);
}
// then if any controls exist, scroll the first control into view
if (layout.Controls.Count > 0)
{
layout.ScrollControlIntoView(layout.Controls[0]);
}

How to replace a specific view in iCarousel on tap/select?

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.

Problems with creating combined image view / camera view window using EmguCV & Windows Forms

Basically, what I'm trying to do is to create a window with list of images and large image view box. If user points at one of the images in the list, imagebox should display it, otherwise it should display live feed from a webcam.
I'm trying to achieve that by using ImageGrabbed event of EmguCV.Capture, and a field that stores index of the currently viewed image. Camera feed works alright, however, when I move the mouse over listbox, image view merely stops updating. It stays stuck at the last frame it saw, and doesn't display the image I'm pointing at. I've checked that the list has images in it, that index is correct, and that image coming from the camera is different from ones saved in the list. Also, same behavior happens when I use vanila PictureBox.
See the code (stripped of extra parts) below.
Initialization:
int ViewedFrame = -1;
var Frames = new List<Image<Rgb, byte>>();
// This list will have some images added to it along the way.
// They will be displayed in a custom drawn ListBox bound to the list.
Camera = new Emgu.CV.Capture(cameraid);
Camera.SetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_WIDTH, width);
Camera.SetCaptureProperty(Emgu.CV.CvEnum.CAP_PROP.CV_CAP_PROP_FRAME_HEIGHT, height);
Camera.ImageGrabbed += UpdateCamera;
Camera.Start();
Frame capture:
private void UpdateCamera(object sender, System.EventArgs ev)
{
if (ViewedFrame == -1)
Preview.Image = Camera.RetrieveBgrFrame();
else
// !!! this line has no apparent effect, image in the ImageBox is still the same
Preview.Image = Frames[ViewedFrame];
}
Mouse handling:
private void FrameList_MouseMove(object sender, MouseEventArgs e)
{
int index = FrameList.IndexFromPoint(e.Location);
if (index == ListBox.NoMatches)
ViewedFrame = -1;
else if (index != ViewedFrame)
{
ViewedFrame = index;
}
}
private void FrameList_MouseLeave(object sender, EventArgs e)
{
ViewedFrame = -1;
}
UPD: I tried adding Preview.Update() and Preview.Refresh() into the same branch as Frames[ViewedFrame], and after the conditional operator. In first case it doesn't have any effect. In second case nothing is shown at all. In case the ImageBox can't keep up updating itself, I enabled doublebuffering, but that didn't help either. Moreover, after I hover the mouse over an image in the list, the camera stream breaks too. The code above is the only one that's somewhat working.

Silverlight 5 new PivotViewer - detect when trading cards have completed rendering

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)

Categories

Resources