Get Device Properties using Xamarin Forms? - xamarin

i'm designing a cross platform app using xamarin forms.
every page/view/Form designed from code behind. now i want to read Height and Width of the device used by user. based on those values, i want to place some header and footers.

To get the screen width (or height) within a Xamarin.Forms solution, I usually add the following few lines of code:
Define a public static property in the shared code, preferably in App.cs:
static public int ScreenWidth;
Initialize it for iOS at the beginning of FinishedLaunching in AppDelegate.cs:
App.ScreenWidth = (int)UIScreen.MainScreen.Bounds.Width;
Initialize it for Android in OnCreate of MainActivity.cs (as described here)
App.ScreenWidth = (int)(Resources.DisplayMetrics.WidthPixels / Resources.DisplayMetrics.Density);
(By dividing by the density this yields device independent pixels.)
I didn't work with Windows Phone, but there should be an equivalent command. And of course, getting the screen height works similarly.
Now you can access App.ScreenWidth anywhere in your code.

In Xamarin Forms Labs here there are classes and examples for getting Device screen information like what you are after.
There are some further notes on implementing this and getting the Device object that you require here.
On a different note, if your only on about placing Headers and Footers then why not use the inbuilt Xamarin.Forms controls to auto-expand controls and layouts etc, that will adapt automatically based to screen of the user's device?
I get the impression that you are looking to go down an AbsoluteLayout approach and specify values yourself? If so, there really is no need. Especially for Headers and Footers of a Layout?

I do this in my viewmodel and it works great. You could do the same in your code-behind.
public SomeViewModel
{
private double width;
private double Width
{
get { return width; }
set
{
width = value;
if (value != 0 && value != -1)
{
// device width found, set other properties
SetProperties();
}
}
}
void SetProperties()
{
// use value of Width however you need
}
public SomeViewModel()
{
Width = Application.Current.MainPage.Width;
var fireAndForget = Task.Run(GetWidthAsync);
}
public async Task GetWidthAsync()
{
while (Width == -1 || Width == 0)
{
await Task.Delay(TimeSpan.FromMilliseconds(1)).ConfigureAwait(false);
// MainPage is the page that is using this ViewModel
Width = Application.Current.MainPage.Width;
}
}
}
If you want to display Width in a label in your form for testing purposes using binding, don't forget to change the property from private to public.

Related

OnAppearing different on iOS and Android

I have found that on iOS, OnAppearing is called when the page literally appears on the screen, whereas on Android, it's called when it's created.
I'm using this event to lazily construct an expensive to construct view but obviously the Android behaviour defeats this.
Is there some way of knowing on Android when a screen literally appears on the screen?
You can use the event:
this.Appearing += YourPageAppearing;
Otherwise, you should use the methods of the Application class that contains the lifecycle methods:
protected override void OnStart()
{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}
On Android, Xamarin.Forms.Page.OnAppearing is called immediately before the page's view is shown to user (not when the page is "created" (constructed)).
If you want an initial view to appear quickly, by omitting an expensive sub-view, use a binding to make that view's IsVisible initially be "false". This will keep it out of the visual tree, avoiding most of the cost of building it. Place the (invisible) view in a grid cell, whose dimensions are constant (either in DPs or "*" - anything other than "Auto".) So that layout will be "ready" for that view, when you make it visible.
APPROACH 1:
Now you just need a binding in view model that will change IsVisible to "true".
The simplest hack is to, in OnAppearing, fire an action that will change that variable after 250 ms.
APPROACH 2:
The clean alternative is to create a custom page renderer, and override "draw".
Have draw, after calling base.draw, check an action property on your page.
If not null, invoke that action, then clear it (so only happens once).
I do this by inheriting from a custom page base class:
XAML for each of my pages (change "ContentPage" to "exodus:ExBasePage"):
<exodus:ExBasePage
xmlns:exodus="clr-namespace:Exodus;assembly=Exodus"
x:Class="YourNamespace.YourPage">
...
</exodus:ExBasePage>
xaml.cs:
using Exodus;
// After creating page, change "ContentPage" to "ExBasePage".
public partial class YourPage : ExBasePage
{
...
my custom ContentPage. NOTE: Includes code not needed for this, related to iOS Safe Area and Android hardward back button:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Exodus
{
public abstract partial class ExBasePage : ContentPage
{
public ExBasePage()
{
// Each sub-class calls InitializeComponent(); not needed here.
ExBasePage.SetupForLightStatusBar( this );
}
// Avoids overlapping iOS status bar at top, and sets a dark background color.
public static void SetupForLightStatusBar( ContentPage page )
{
page.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea( true );
// iOS NOTE: Each ContentPage must set its BackgroundColor to black or other dark color (when using LightContent for status bar).
//page.BackgroundColor = Color.Black;
page.BackgroundColor = Color.FromRgb( 0.3, 0.3, 0.3 );
}
// Per-platform ExBasePageRenderer uses these.
public System.Action NextDrawAction;
/// <summary>
/// Override to do something else (or to do nothing, i.e. suppress back button).
/// </summary>
public virtual void OnHardwareBackButton()
{
// Normal content page; do normal back button behavior.
global::Exodus.Services.NavigatePopAsync();
}
}
}
renderer in Android project:
using System;
using Android.Content;
using Android.Views;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Exodus;
using Exodus.Android;
[assembly: ExportRenderer( typeof( ExBasePage ), typeof( ExBasePageRenderer ) )]
namespace Exodus.Android
{
public class ExBasePageRenderer : PageRenderer
{
public ExBasePageRenderer( Context context ) : base( context )
{
}
protected override void OnElementChanged( ElementChangedEventArgs<Page> e )
{
base.OnElementChanged( e );
var page = Element as ExBasePage;
if (page != null)
page.firstDraw = true;
}
public override void Draw( Canvas canvas )
{
try
{
base.Draw( canvas );
var page = Element as ExBasePage;
if (page?.NextDrawAction != null)
{
page.NextDrawAction();
page.NextDrawAction = null;
}
}
catch (Exception ex)
{
// TBD: Got Disposed exception on Android Bitmap, after rotating phone (in simulator).
// TODO: Log exception.
Console.WriteLine( "ExBasePageRenderer.Draw exception: " + ex.ToString() );
}
}
}
}
To do some action after the first time the page is drawn:
public partial class YourPage : ExBasePage
{
protected override void OnAppearing()
{
// TODO: OnPlatform code - I don't have it handy.
// On iOS, we call immediately "DeferredOnAppearing();"
// On Android, we set this field, and it is done in custom renderer.
NextDrawAction = DeferredOnAppearing;
}
void DeferredOnAppearing()
{
// Whatever you want to happen after page is drawn first time:
// ((MyViewModel)BindingContext).ExpensiveViewVisible = true;
// Where MyViewModel contains:
// public bool ExpensiveViewVisible { get; set; }
// And your XAML contains:
// <ExpensiveView IsVisible={Binding ExpensiveViewVisible}" ... />
}
}
NOTE: I do this differently on iOS, because Xamarin Forms on iOS (incorrectly - not to spec) calls OnAppearing AFTER the page is drawn.
So I have OnPlatform logic. On iOS, OnAppearing immediately calls DeferredOnAppearing. On Android, the line shown is done.
Hopefully iOS will eventually be fixed to call OnAppearing BEFORE,
for consistency between the two platforms.
If so, I would then add a similar renderer for iOS.
(The current iOS implementation means there is no way to update a view before it appears a SECOND time, due to popping the nav stack.
instead, it appears with outdated content, THEN you get a chance
to correct it. This is not good.)

Custom FireMonkey component does not get drawn

Working in C++Builder 10.2 Tokyo, I am trying to add a custom component to a FireMonkey TForm programmatically at runtime.
The custom component is not installed as a package and registered in the IDE (as that ended up complicating the project too much), rather it is simply a subclass of TPanel.
However, The component, and its children, do not get drawn when I run the application. I have tested this on Windows and Android, and tried multiple modifications, like setting the Width and Height explicitly.
How can I fix this?
Below is the relevant bit of my code:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm3D(Owner)
{
mkView = new MKView(this);
mkView->Align = TAlignLayout::Client;
mkView->Enabled = true;
mkView->Visible = true;
mkView->Parent = this;
}
__fastcall MKView::MKView(TComponent *Owner)
: TPanel(Owner)
{
this->OnMouseDown = MKView_OnMouseDown;
TLabel1 = new TLabel(this);
TLabel1->Text = "Here I am!";
TLabel1->Enabled = true;
TLabel1->Visible = true;
TLabel1->Parent = this;
TLabel1->OnMouseDown = MKView_OnMouseDown;
}
It looks like TForm3D doesn't work well with standard FireMonkey components, as it is designed for rendering FireMonkey 3D components and uses OnRender() instead of OnPaint(). I was using TForm3D for its OpenGL context, but having switched to a standard TForm the components are now being drawn.

Is there a Xamarin Mvvmcross Android Shared Element Navigation example?

I'm trying to get this animation/transition working in my Xamarin Android application with Mvx.
I have a recyclerview with cards. When tapping on a card, I now call:
private void TimeLineAdapterOnItemClick(object sender, int position)
{
TimeLineAdapter ta = (TimeLineAdapter) sender;
var item = ta.Items[position];
int photoNum = position + 1;
Toast.MakeText(Activity, "This is photo number " + photoNum, ToastLength.Short).Show();
ViewModel.ShowDetails(item.Id);
}
I'm trying to find out how to translate this java navigation with transition to Xamarin with Mvvmcross:
ActivityOptionsCompat options =
ActivityOptionsCompat.MakeSceneTransitionAnimation(this, imageView, getString(R.string.activity_image_trans));
startActivity(intent, options.toBundle());
I know that within Mvx you can make use of custom presenters, but how do I get hold of, for example, the ImageView of the tapped Card within the RecyclerView which I would like to 'transform' to the new ImageView on the new Activity?
Thanks!
.
Is there a Xamarin Mvvmcross Android Shared Element Navigation
example?
I do not believe so.
I know that within Mvx you can make use of custom presenters, but how
do I get hold of, for example, the ImageView of the tapped Card within
the RecyclerView which I would like to 'transform' to the new
ImageView on the new Activity?
The easiest way that I can think of to achieve the sharing of control elements you want to transition is via the use of view tags and a presentation bundle when using ShowViewModel.
I would suggest making some changes to your Adapter Click handler to include the view of the ViewHolder being selected (See GitHub repo for example with EventArgs). That way you can interact with the ImageView and set a tag that can be used later to identity it.
private void TimeLineAdapterOnItemClick(object sender, View e)
{
var imageView = e.FindViewById<ImageView>(Resource.Id.imageView);
imageView.Tag = "anim_image";
ViewModel.ShowDetails(imageView.Tag.ToString());
}
Then in your ViewModel, send that tag via a presentationBundle.
public void ShowDetails(string animationTag)
{
var presentationBundle = new MvxBundle(new Dictionary<string, string>
{
["Animate_Tag"] = animationTag
});
ShowViewModel<DetailsViewModel>(presentationBundle: presentationBundle);
}
Then create a custom presenter to pickup the presentationBundle and handle the creating of new activity with the transition. The custom presenter which makes use of the tag to find the element that you want to transition and include the ActivityOptionsCompat in the starting of the new activity. This example is using a MvxFragmentsPresenter but if you are not making use of fragments and using MvxAndroidViewPresenter the solution would be almost identical (Override Show instead and no constructor required).
public class SharedElementFragmentsPresenter : MvxFragmentsPresenter
{
public SharedElementFragmentsPresenter(IEnumerable<Assembly> AndroidViewAssemblies)
: base(AndroidViewAssemblies)
{
}
protected override void ShowActivity(MvxViewModelRequest request, MvxViewModelRequest fragmentRequest = null)
{
if (InterceptPresenter(request))
return;
Show(request, fragmentRequest);
}
private bool InterceptPresenter(MvxViewModelRequest request)
{
if ((request.PresentationValues?.ContainsKey("Animate_Tag") ?? false)
&& request.PresentationValues.TryGetValue("Animate_Tag", out var controlTag))
{
var intent = CreateIntentForRequest(request);
var control = Activity.FindViewById(Android.Resource.Id.Content).FindViewWithTag(controlTag);
control.Tag = null;
var transitionName = control.GetTransitionNameSupport();
if (string.IsNullOrEmpty(transitionName))
{
Mvx.Warning($"A {nameof(transitionName)} is required in order to animate a control.");
return false;
}
var activityOptions = ActivityOptionsCompat.MakeSceneTransitionAnimation(Activity, control, transitionName);
Activity.StartActivity(intent, activityOptions.ToBundle());
return true;
}
return false;
}
}
GetTransitionNameSupport is an extension method that just does a platform API check when getting the TransitionName.
public static string GetTransitionNameSupport(this ImageView imageView)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
return imageView.TransitionName;
return string.Empty;
}
The final step would be to register the custom presenter in you Setup.cs
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var mvxPresenter = new SharedElementFragmentsPresenter(AndroidViewAssemblies);
Mvx.RegisterSingleton<IMvxAndroidViewPresenter>(mvxPresenter);
return mvxPresenter;
}
You can check the repo on GitHub which demonstrates this example. The solution is designed so that the presenter does not have to care about the type of the control that is being transitioned. A control only requires a tag used to identify it. The example in the repo also allows for specifying multiple control elements that you want to transition (I did not want to include more complexity in the example above).

Monotouch Dialog OwnerDrawnElement - Height Size not being called

I am trying to inherit from OwnerDrawnElement (as presented in the docs). Everything seems to be working except the Height method is never called so I am not able to set the height of the cell. The Height method is never called (breakpoint never hits). I tried to force a reimplementation of IElementSizing and implemented the method again as a new method. Again, that also doesn't get hit. Here is the stripped down code
public class CustomDrawElement : OwnerDrawnElement, IElementSizing
{
public CustomDrawElement (UITableViewCellStyle style, string cellIdentifier) : base (style, cellIdentifier)
{
}
#region implemented abstract members of OwnerDrawnElement
public override void Draw (System.Drawing.RectangleF bounds, MonoTouch.CoreGraphics.CGContext context, MonoTouch.UIKit.UIView view)
{
UIColor.Blue.SetFill ();
context.FillRect (bounds);
}
public override float Height (System.Drawing.RectangleF bounds)
{
return 220f;
}
#endregion
public new float GetHeight (UITableView tableView, NSIndexPath indexPath) {
return 220f;
}
}
When creating the object, I using for test purposes only:
var e = new CustomDrawnElement(UITableViewCellStyle.Default, "myKey");
I am currently using the latest Xamarin Studio 3.
I have read every question regarding this class and no one seems to have had this problem. Not sure if it is due to the new 3 release that introduced a new bug.
Ok, after much research, I found the answer. In order for the custom height methods to be called, you need to set UnevenRows = true on the rootElement created for MonoDialog.

How to construct simple wxWidgets image display

I wrote a wxPython program that I am translating to wxWidgets. The program has a scrolled window that displays an image. Following Rappin, wxPython In Action (Listing 12.1), I used a StaticBitmap within a panel. While surfing the latest wxWidgets documentation, I found a dire warning that wxStaticBitmap should only be used for very small images. It says, "... you should use your own control if you want to display larger images portably." Okay. Show me. I don't have my "own control."
Was Rappin wrong, or is the documentation out of date?
The question - a newbie one, no doubt - is what is the right way to do a simple image-view window in wxWidgets? A drop-in replacement for wxStaticBitmap would be nice. I looked into the "image" program in the wxWidgets "samples" directory. It's as long a War and Peace. Surely there must be a canned class or a simple recipe.
Don't let the size of the "image" sample fool you, only a few lines of code are necessary to do what you want.
Search for the MyImageFrame class in the image.cpp file, it is nothing more than a class with a private bitmap field, a custom constructor to set the bitmap and the window client size, and an event handler for EVT_PAINT:
void OnPaint(wxPaintEvent& WXUNUSED(event))
{
wxPaintDC dc( this );
dc.DrawBitmap( m_bitmap, 0, 0, true /* use mask */ );
}
Since you don't want a frame class here's your recipe: You create a simple descendant of wxWindow that has a similar constructor, paint handler and duplicates the methods of wxStaticBitmap that you use in your code. Maybe simply one method to set a new bitmap and resize the control to the new bitmap dimensions.
// A scrolled window for showing an image.
class PictureFrame: public wxScrolledWindow
{
public:
PictureFrame()
: wxScrolledWindow()
, bitmap(0,0)
{;}
void Create(wxWindow *parent, wxWindowID id = -1)
{
wxScrolledWindow::Create(parent, id);
}
void LoadImage(wxImage &image) {
bitmap = wxBitmap(image);
SetVirtualSize(bitmap.GetWidth(), bitmap.GetHeight());
wxClientDC dc(this);
PrepareDC(dc);
dc.DrawBitmap(bitmap, 0, 0);
}
protected:
wxBitmap bitmap;
void OnMouse(wxMouseEvent &event) {
int xx,yy;
CalcUnscrolledPosition(event.GetX(), event.GetY(), &xx, &yy);
event.m_x = xx; event.m_y = yy;
event.ResumePropagation(1); // Pass along mouse events (e.g. to parent)
event.Skip();
}
void OnPaint(wxPaintEvent &event) {
wxPaintDC dc(this);
PrepareDC(dc);
dc.DrawBitmap(bitmap, 0,0, true);
}
private:
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(PictureFrame,wxScrolledWindow)
EVT_PAINT(PictureFrame::OnPaint)
EVT_MOUSE_EVENTS(PictureFrame::OnMouse)
END_EVENT_TABLE()

Resources