Skip to content

Instantly share code, notes, and snippets.

@FlavioGoncalves-Cayas
Last active May 20, 2024 15:43
Show Gist options
  • Save FlavioGoncalves-Cayas/841a13ca1f2904ef1f732c159b4c5f5d to your computer and use it in GitHub Desktop.
Save FlavioGoncalves-Cayas/841a13ca1f2904ef1f732c159b4c5f5d to your computer and use it in GitHub Desktop.
.NET MAUI OnOrientationExtension

.NET MAUI OnOrientationExtension

This is the accompanying Gist to the blog post at cayas.de.

The OnOrientation MarkupExtension helps to make XAML pages automatically adapt to orientation changes. By enabling developers to provide different values for their view's properties directly inline in XAML, similiar to other MarkupExtensions like OnPlatform and OnIdiom.

For example it can be used to rearrange a Grid's ColumnDefinitions to accommodate for orientation changes:

<Grid ColumnDefinitions="{OnOrientation Default='*', Landscape='*,*', TypeConverter={x:Type ColumnDefinitionCollectionTypeConverter}}">

(Why ColumnDefinitionCollectionTypeConverter needs to be specified here is explained below or in the blog post)

How to use

  1. Install CommunityToolkit.Maui into the project
  2. Add UseMauiCommunityToolkit() to MauiProgram.cs when configuring the MauiAppBuilder
  3. Add both classes, OnOrientationExtension.cs and OnOrientationSource.cs, to the .NET MAUI project
  4. Add the namespace alias to the XAML file
  5. Use OnOrientation either inline as MarkupExtension in attribute syntax, or via property element syntax

Make sure to always specify a value for Default.

Inline

<Label Grid.Row="{OnOrientation Default=0, Landscape=1}" />

Since the values you provide in a MarkupExtension must be of a primitive type like int or string and type conversion using TypeConverters does not kick in, sometimes you may need to use x:Static.

<Label HorizontalOptions="{OnOrientation Default={x:Static LayoutOptions.Start}, Landscape={x:Static LayoutOptions.Center}}" />

If the value is more complex and you know which TypeConverter should be used, you can specify it. OnOrientation can then perform type conversion using the provided Type of TypeConverter.

<Grid ColumnDefinitions="{OnOrientation Default='*', Landscape='*,*', TypeConverter={x:Type ColumnDefinitionCollectionTypeConverter}}">

Property Element Syntax

When a complex value is needed and no TypeConverter exists, then you can also use property element syntax to specify the different values. Although one can argue that this is way too verbose for practical use.

<Grid.ColumnDefinitions>
    <ext:OnOrientation>
        <ext:OnOrientation.Default>
            <ColumnDefinitionCollection>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="0" />
            </ColumnDefinitionCollection>
        </ext:OnOrientation.Default>
        <ext:OnOrientation.Landscape>
            <ColumnDefinitionCollection>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </ColumnDefinitionCollection>
        </ext:OnOrientation.Landscape>
    </ext:OnOrientation>
</Grid.ColumnDefinitions>

This syntax could also be used to switch out entire views.

<ContentView>
    <ContentView.Content>
        <ext:OnOrientation>
            <Label Text="This text is visible in Default" />
            <ext:OnOrientation.Landscape>
                <Label Text="This text is visible in Landscape" />
            </ext:OnOrientation.Landscape>
        </ext:OnOrientation>
    </ContentView.Content>
</ContentView>

Limitations

  • Only works with Android and iOS, since OnOrientationExtension uses the Orientation provided by DeviceDisplay.MainDisplayInfo.Orientation
  • Value cannot be a BindingExpression
[ContentProperty(nameof(Default))]
public class OnOrientationExtension : IMarkupExtension<BindingBase>
{
public Type TypeConverter { get; set; }
public object Default { get; set; }
public object Landscape { get; set; }
public object Portrait { get; set; }
static OnOrientationExtension()
{
DeviceDisplay.MainDisplayInfoChanged += (_, _) => WeakReferenceMessenger.Default.Send(new OrientationChangedMessage());
}
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var typeConverter = TypeConverter != null ? (TypeConverter)Activator.CreateInstance(TypeConverter) : null;
var orientationSource = new OnOrientationSource { DefaultValue = typeConverter?.ConvertFromInvariantString((string)Default) ?? Default };
orientationSource.PortraitValue = Portrait == null ? orientationSource.DefaultValue : typeConverter?.ConvertFromInvariantString((string)Portrait) ?? Portrait;
orientationSource.LandscapeValue = Landscape == null ? orientationSource.DefaultValue : typeConverter?.ConvertFromInvariantString((string)Landscape) ?? Landscape;
return new Binding
{
Mode = BindingMode.OneWay,
Path = nameof(OnOrientationSource.Value),
Source = orientationSource
};
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) => ProvideValue(serviceProvider);
public class OrientationChangedMessage
{
}
}
public class OnOrientationSource : INotifyPropertyChanged
{
private object _defaultValue;
private object _portraitValue;
private object _landscapeValue;
public object DefaultValue
{
get => _defaultValue;
set
{
_defaultValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
public object PortraitValue
{
get => _portraitValue;
set
{
_portraitValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
public object LandscapeValue
{
get => _landscapeValue;
set
{
_landscapeValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
public object Value => DeviceDisplay.MainDisplayInfo.Orientation switch
{
DisplayOrientation.Portrait => PortraitValue ?? DefaultValue,
DisplayOrientation.Landscape => LandscapeValue ?? DefaultValue,
_ => DefaultValue
};
public OnOrientationSource()
{
WeakReferenceMessenger.Default.Register<OnOrientationSource,OnOrientationExtension.OrientationChangedMessage>(this, OnOrientationChanged);
}
private void OnOrientationChanged(OnOrientationSource r, OnOrientationExtension.OrientationChangedMessage m)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment