Last week, I published the first in a series of articles on building cross-platform mobile apps with Visual Studio 2015 and Xamarin Forms. In it, I presented an RPN calculator app that works on Windows Phone, Android, and iOS.
One subject I didn’t address in that article was how to respond to orientation changes in a Xamarin Forms app – that is, when the device goes from portrait mode to landscape or vice versa. By default, the screen rotates so that the content remains right-side-up. You can “lock” the orientation to prevent the screen from rotating (more on that in a moment), but the more common case is that you want to change what’s on the screen to adapt to the new dimensions.
I built a second version of my calculator app that does just that. When the device rotates to landscape mode, two additional columns of buttons appear on the left side of the screen – buttons for sin, cos, tan, and so on:
Most mobile operating systems fire some type of event when the device orientation changes. Xamarin Forms does not, at least not yet. (Xamarin Forms is a work in progress and it’s evolving rapidly. It hasn’t escaped the notice of the engineers in charge that the platform lacks a device-orientation event, so don’t be surprised if one is added soon.) In the meantime, the recommended way to respond to orientation changes is to override the page’s virtual OnSizeAllocated method and make any changes you wish to make inside the override. Here’s a simple example that writes to debug output when the orientation changes and indicates whether the device is in portrait mode or landscape mode:
protected override void OnSizeAllocated(double width, double height) { base.OnSizeAllocated(width, height); Debug.WriteLine(width > height ? "Landscape" : "Portrait"); }
There are a few caveats to be aware of when overriding OnSizeAllocated:
The upshot of bullets 1 and 2 is that it’s smart to build logic into the override to prevent the layout changes you make from being applied multiple times following a single change in orientation.
Here’s the code I added to CalculatorPage.xaml.cs to make additional buttons appear in landscape mode and disappear in portrait mode:
protected override void OnSizeAllocated(double width, double height) { base.OnSizeAllocated(width, height); // Important! if (width != _width || height != _height) { _width = width; _height = height; ShowExtraButtons(width > height); } } private void ShowExtraButtons(bool visible) { foreach (View child in LayoutRoot.Children) { if (child is Button && (int)child.GetValue(Grid.ColumnProperty) < 2) { child.IsVisible = visible; } } }
The ShowExtraButtons method enumerates the buttons in the Grid control named “LayoutRoot” and either shows or hides the ones in the left two columns by setting their IsVisible properties to true or false. (Incidentally, Microsoft XAML developers are accustomed to controlling visibility using the Visibility property; In Xamarin XAML, you use IsVisible instead. It’s a Boolean, so you won’t have to write those silly Boolean-to-Visibility value converters any more.) _width and _height are private fields I declared in the page to store the last width and height passed to OnSizeAllocated. They’re used to prevent ShowExtraButtons from being called more than once when OnSizeAllocated fires several times in a row with the same width and height parameters.
Where did the extra two columns of buttons come from? I modified CalculatorPage.xaml to declare them. Here’s the modified version. The new buttons are declared near the bottom, after the comment that reads “Buttons visible only in landscape mode:”
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XFormsRPNCalculator.CalculatorPage" xmlns:local="clr-namespace:XFormsRPNCalculator;assembly=XFormsRPNCalculator" Padding="16"> <ContentPage.BackgroundColor> <OnPlatform x:TypeArguments="Color" iOS="#fff0f0f0" Android="Black" /> </ContentPage.BackgroundColor> <ContentPage.Resources> <ResourceDictionary> <Style TargetType="Button"> <Setter Property="BorderColor"> <Setter.Value> <OnPlatform x:TypeArguments="Color" WinPhone="#FF404040" /> </Setter.Value> </Setter> <Setter Property="BackgroundColor"> <Setter.Value> <OnPlatform x:TypeArguments="Color" iOS="White" WinPhone="#FF303030" /> </Setter.Value> </Setter> </Style> </ResourceDictionary> </ContentPage.Resources> <Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition Height="1.6*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowSpacing> <OnPlatform x:TypeArguments="x:Double" iOS="8" WinPhone="-10" /> </Grid.RowSpacing> <Grid.ColumnSpacing> <OnPlatform x:TypeArguments="x:Double" iOS="8" WinPhone="-10" /> </Grid.ColumnSpacing> <ContentView Padding="8" Grid.Row="0" Grid.ColumnSpan="6"> <Label x:Name="Output" Text="{Binding Output}" Grid.Column="0" XAlign="End"> <Label.Font> <OnPlatform x:TypeArguments="Font" iOS="36" Android="48" WinPhone="56" /> </Label.Font> </Label> </ContentView> <Button Text="±" Grid.Row="1" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="±" /> <Button Text="EXP" Grid.Row="1" Grid.Column="3" Command="{Binding CalculatorCommand}" CommandParameter="EXP" /> <Button Text="STO" Grid.Row="1" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="STO" /> <Button Text="RCL" Grid.Row="1" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="RCL" /> <Button Text="ENTER" Grid.Row="2" Grid.ColumnSpan="2" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="ENTER" /> <Button Text="FIX" Grid.Row="2" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="FIX" /> <Button Text="CLX" Grid.Row="2" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="CLX" /> <Button Text="-" Grid.Row="3" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="-" /> <Button Text="7" Grid.Row="3" Grid.Column="3" Command="{Binding CalculatorCommand}" CommandParameter="7" /> <Button Text="8" Grid.Row="3" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="8" /> <Button Text="9" Grid.Row="3" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="9" /> <Button Text="+" Grid.Row="4" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="+" /> <Button Text="4" Grid.Row="4" Grid.Column="3" Command="{Binding CalculatorCommand}" CommandParameter="4" /> <Button Text="5" Grid.Row="4" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="5" /> <Button Text="6" Grid.Row="4" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="6" /> <Button Text="x" Grid.Row="5" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="x" /> <Button Text="1" Grid.Row="5" Grid.Column="3" Command="{Binding CalculatorCommand}" CommandParameter="1" /> <Button Text="2" Grid.Row="5" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="2" /> <Button Text="3" Grid.Row="5" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="3" /> <Button Text="÷" Grid.Row="6" Grid.Column="2" Command="{Binding CalculatorCommand}" CommandParameter="÷" /> <Button Text="0" Grid.Row="6" Grid.Column="3" Command="{Binding CalculatorCommand}" CommandParameter="0" /> <Button Text="." Grid.Row="6" Grid.Column="4" Command="{Binding CalculatorCommand}" CommandParameter="." /> <Button Text="DEL" Grid.Row="6" Grid.Column="5" Command="{Binding CalculatorCommand}" CommandParameter="DEL" /> <!-- Buttons visible only in landscape mode --> <Button Text="sin" Grid.Row="1" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="sin" IsVisible="False" /> <Button Text="cos" Grid.Row="2" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="cos" IsVisible="False" /> <Button Text="tan" Grid.Row="3" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="tan" IsVisible="False" /> <Button Text="asin" Grid.Row="4" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="asin" IsVisible="False" /> <Button Text="acos" Grid.Row="5" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="acos" IsVisible="False" /> <Button Text="atan" Grid.Row="6" Grid.Column="0" Command="{Binding CalculatorCommand}" CommandParameter="atan" IsVisible="False" /> <Button Text="1/x" Grid.Row="1" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="1/x" IsVisible="False" /> <Button Text="sqrt" Grid.Row="2" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="sqrt" IsVisible="False" /> <Button Text="x²" Grid.Row="3" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="x²" IsVisible="False" /> <Button Text="log" Grid.Row="4" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="log" IsVisible="False" /> <Button Text="ln" Grid.Row="5" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="ln" IsVisible="False" /> <Button Text="p" Grid.Row="6" Grid.Column="1" Command="{Binding CalculatorCommand}" CommandParameter="p" IsVisible="False" /> </Grid> </ContentPage>
The extra buttons are visible in landscape mode only. Because the width of the columns they’re in is “Auto” rather “*,” the columns collapse when the buttons disappear. An “Auto” column sizes itself to match the width of its widest element.
What if you’re writing a game and don’t want the screen to rotate when the device rotates? How do you lock the screen orientation in a Xamarin Forms app?
For an iOS app, you can open the project properties in Visual Studio, go to the “iOS Application” tab, and pick the orientations you wish to support. If you uncheck everything but ”Portrait,” the screen will remain locked in portrait mode:
For Android, you need to open MainActivity.cs in the Android project and add a ScreenOrientation property to the Activity attribute:
[Activity(Label = "MyAwesomePortableApp", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]
Be aware that that due to a bug in Microsoft’s Android emulator, the screen will often rotate anyway. Presumably this bug will be fixed in the not-too-distant future.
For Windows Phone, the screen rotates when the device rotates due to the following statement in the MainPage constructor in MainPage.xaml.cs in the Windows Phone project:
SupportedOrientations = SupportedPageOrientation.PortraitOrLandscape;
To lock the orientation to portrait, modify this statement as follows:
SupportedOrientations = SupportedPageOrientation.Portrait;
As an alternative, you can delete this statement altogether and modify the corresponding SupportedOrientations attribute in MainPage.xaml.
You can download version 2 of the calculator app from my OneDrive. Feel free to share it with your colleagues. And stay tuned for more articles about Xamarin Forms, because we’ve only scratched the surface of this awesome new platform.
Cloud management is difficult to do manually, especially if you work with multiple cloud…
Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…
https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…
FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…
Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…
In the intricate landscape of modern business, compliance is both a cornerstone of operational integrity…
View Comments
awesome code, thanks
awesome code, thanks
from where do we get the width and height of the device ?
can anyone help me out with microsoft visual studio ? i want the app to be responsive
from where do we get the width and height of the device ?
can anyone help me out with microsoft visual studio ? i want the app to be responsive
Hi, thanks for the great article. It looks like the code has now gone, would it be possible to post a new link?
Link is fixed now. Sorry about that!
Thanks Jeff but if I click on the link http://1drv.ms/1EYM0tl it says 'This link doesn't work anymore'. Does it work ok for you?
Try https://1drv.ms/u/s!Av8u_NJzP1I4g_Nganzb7AQwx4Y0DQ
Great thanks Jeff, that works!
Hi, thanks for the great article. It looks like the code has now gone, would it be possible to post a new link?
Link is fixed now. Sorry about that!
Thanks Jeff but if I click on the link http://1drv.ms/1EYM0tl it says 'This link doesn't work anymore'. Does it work ok for you?
Try https://1drv.ms/u/s!Av8u_NJzP1I4g_Nganzb7AQwx4Y0DQ
Great thanks Jeff, that works!